[
  {
    "path": ".babelrc",
    "content": "{\n  \"plugins\": [\n    \"@babel/plugin-proposal-class-properties\",\n    [\"@babel/plugin-proposal-decorators\", { \"decoratorsBeforeExport\": true }]\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# editorconfig.org\n\nroot = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\n\n[*.{diff,md}]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslint-doc-generatorrc.js",
    "content": "const path = require('path');\n\nconst HBS_ONLY_NOTE =\n  '> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.';\n\nconst END_HEADER_MARKER = '<!-- end auto-generated rule header -->';\n\n/** @type {import('eslint-doc-generator').GenerateOptions} */\nmodule.exports = {\n  configEmoji: [\n    ['recommended-gjs', '![gjs logo](/docs/svgs/gjs.svg)'],\n    ['recommended-gts', '![gts logo](/docs/svgs/gts.svg)'],\n    ['template-lint-migration', '📋'],\n  ],\n  ruleDocSectionInclude: ['Examples'],\n  ruleDocTitleFormat: 'prefix-name',\n  ruleListSplit: 'meta.docs.category',\n  urlConfigs: 'https://github.com/ember-cli/eslint-plugin-ember#-configurations',\n  postprocess(content, filePath) {\n    // Only process rule doc files\n    if (!filePath.includes(path.join('docs', 'rules'))) {\n      return content;\n    }\n\n    const ruleName = path.basename(filePath, '.md');\n\n    let rule;\n    try {\n      rule = require(path.join(__dirname, 'lib', 'rules', ruleName));\n    } catch {\n      return content;\n    }\n\n    // Strip any existing HBS Only note (with surrounding blank lines)\n    let result = content.replace(/\\n> \\*\\*HBS Only\\*\\*:[^\\n]+\\n/, '\\n');\n\n    // Add HBS Only note for loose-mode rules\n    if (rule.meta?.docs?.templateMode === 'loose') {\n      result = result.replace(END_HEADER_MARKER, `${HBS_ONLY_NOTE}\\n\\n${END_HEADER_MARKER}`);\n    }\n\n    return result;\n  },\n};\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/renovate.json5",
    "content": "// Docs:\n// https://docs.renovatebot.com/configuration-options/\n{\n  \"extends\": [\n    \"github>NullVoxPopuli/renovate:npm.json5\"\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/bench-compare.yml",
    "content": "name: Benchmark Comparison\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\nconcurrency:\n  group: bench-${{ github.head_ref }}\n  cancel-in-progress: true\n\njobs:\n  bench-compare:\n    name: 'Benchmark Comparison'\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      contents: read\n\n    steps:\n      - uses: actions/checkout@v6\n        id: checkout\n        with:\n          # Full history so the script can git-archive the base branch.\n          fetch-depth: 0\n          # Use the PR head SHA so fork PRs resolve correctly\n          # (github.head_ref is a branch name that only exists on the fork remote).\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - uses: wyvox/action-setup-pnpm@v4\n\n      - name: Run benchmark comparison\n        env:\n          BENCH_JSON_OUTPUT: ${{ runner.temp }}/bench-results.json\n        run: |\n          set -o pipefail\n          pnpm bench:compare | sed 's/\\x1b\\[[0-9;]*m//g' > \"$RUNNER_TEMP/bench-output.txt\"\n\n      - name: Format PR comment\n        if: always() && steps.checkout.outcome == 'success'\n        env:\n          BENCH_OUTPUT_FILE: ${{ runner.temp }}/bench-output.txt\n          BENCH_JSON_OUTPUT: ${{ runner.temp }}/bench-results.json\n          BENCH_JOB_SUCCESS: ${{ job.status == 'success' }}\n        run: node scripts/format-bench-comment.mjs > \"$RUNNER_TEMP/bench-comment.md\"\n\n      - name: Write job summary\n        if: always() && steps.checkout.outcome == 'success'\n        run: cat \"$RUNNER_TEMP/bench-comment.md\" >> \"$GITHUB_STEP_SUMMARY\"\n\n      - name: Post PR comment\n        if: always() && steps.checkout.outcome == 'success'\n        uses: actions/github-script@v9\n        with:\n          script: |\n            const fs = require('fs');\n            const marker = '<!-- bench-compare -->';\n\n            const body = fs.readFileSync(process.env.RUNNER_TEMP + '/bench-comment.md', 'utf8');\n\n            const headFullName = context.payload.pull_request?.head?.repo?.full_name;\n            const isFork = !headFullName || headFullName !== context.repo.owner + '/' + context.repo.repo;\n\n            if (isFork) {\n              core.info('PR is from a fork — skipping PR comment (results are in the job summary).');\n              core.info('--- Comment body start ---');\n              core.info(body);\n              core.info('--- Comment body end ---');\n            } else {\n              const { data: comments } = await github.rest.issues.listComments({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: context.issue.number,\n              });\n\n              const existing = comments.find(c => c.body.includes(marker));\n\n              if (existing) {\n                await github.rest.issues.updateComment({\n                  owner: context.repo.owner,\n                  repo: context.repo.repo,\n                  comment_id: existing.id,\n                  body,\n                });\n              } else {\n                await github.rest.issues.createComment({\n                  owner: context.repo.owner,\n                  repo: context.repo.repo,\n                  issue_number: context.issue.number,\n                  body,\n                });\n              }\n            }\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions\n\nname: CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  self-lint:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n    - uses: pnpm/action-setup@v5\n      with:\n        run_install: false\n    - uses: actions/setup-node@v6\n      with:\n        node-version: 24.x\n        cache: 'pnpm'\n\n    - run: pnpm install\n    - run: pnpm lint\n\n  build:\n    runs-on: ${{ matrix.os }}-latest\n\n    strategy:\n      matrix:\n        os: [ ubuntu, windows ]\n        node-version: [20.x, 22.x, 24.x]\n\n    steps:\n    - uses: actions/checkout@v6\n    - uses: pnpm/action-setup@v5\n      with:\n        run_install: false\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v6\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'pnpm'\n\n    - run: pnpm install\n    - run: pnpm test:coverage\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    - cron: '18 9 * * 2'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n        # Learn more:\n        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v6\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v4\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/plan-release.yml",
    "content": "name: Plan Release\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n      - master\n  pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/\n    types:\n      - labeled\n      - unlabeled\n\nconcurrency:\n  group: plan-release # only the latest one of these should ever be running\n  cancel-in-progress: true\n\njobs:\n  should-run-release-plan-prepare:\n    name: Should we run release-plan prepare?\n    runs-on: ubuntu-latest\n    outputs:\n      should-prepare: ${{ steps.should-prepare.outputs.should-prepare }}\n    steps:\n      - uses: release-plan/actions/should-prepare-release@v1\n        with:\n          ref: 'master'\n        id: should-prepare\n\n  create-prepare-release-pr:\n    name: Create Prepare Release PR\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    needs: should-run-release-plan-prepare\n    permissions:\n      contents: write\n      issues: read\n      pull-requests: write\n    if: needs.should-run-release-plan-prepare.outputs.should-prepare == 'true'    \n    steps:\n      - uses: release-plan/actions/prepare@v1\n        name: Run release-plan prepare\n        with:\n          ref: 'master'\n        env:\n          GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}\n        id: explanation\n\n      - uses: peter-evans/create-pull-request@v8\n        name: Create Prepare Release PR\n        with:\n          commit-message: \"Prepare Release ${{ steps.explanation.outputs.new-version}} using 'release-plan'\"\n          labels: \"internal\"\n          sign-commits: true\n          branch: release-preview\n          title: Prepare Release ${{ steps.explanation.outputs.new-version }}\n          body: |\n            This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍\n\n            -----------------------------------------\n\n            ${{ steps.explanation.outputs.text }}\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "# For every push to the primary branch with .release-plan.json modified,\n# runs release-plan.\n\nname: Publish Stable\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n      - master\n    paths:\n      - '.release-plan.json'\n\nconcurrency:\n  group: publish-${{ github.head_ref || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  publish:\n    name: \"NPM Publish\"\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      id-token: write\n      attestations: write\n\n    steps:\n      - uses: actions/checkout@v6\n      - uses: pnpm/action-setup@v5\n      - uses: actions/setup-node@v6\n        with:\n          node-version: 24\n          registry-url: 'https://registry.npmjs.org'\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: npm install -g npm@latest # ensure that the globally installed npm is new enough to support OIDC\n      - name: Publish to NPM\n        run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish\n        env:\n          GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\n\n# Dependencies\nnode_modules\n\n# Misc\n.eslintcache\ncoverage\nnpm-debug.log\n*.swp\n.vscode\n\n# eslint-remote-tester\neslint-remote-tester-results\n\n# Benchmark output\nbench-results.json\n\n# Lock file generated by npm install (project uses pnpm)\npackage-lock.json\n"
  },
  {
    "path": ".markdownlint.json",
    "content": "{\n  \"line-length\": false,\n  \"table-column-style\": false,\n  \"ul-style\": {\n    \"style\": \"dash\"\n  }\n}\n"
  },
  {
    "path": ".markdownlintignore",
    "content": "CHANGELOG.md\nLICENSE.md\nnode_modules\n"
  },
  {
    "path": ".npmpackagejsonlintrc.json",
    "content": "{\n  \"rules\": {\n    \"no-duplicate-properties\": \"error\",\n    \"no-repeated-dependencies\": \"error\",\n    \"prefer-alphabetical-bundledDependencies\": \"error\",\n    \"prefer-alphabetical-dependencies\": \"error\",\n    \"prefer-alphabetical-devDependencies\": \"error\",\n    \"prefer-alphabetical-optionalDependencies\": \"error\",\n    \"prefer-alphabetical-scripts\": \"error\",\n    \"prefer-caret-version-dependencies\": \"error\",\n    \"prefer-caret-version-devDependencies\": \"error\",\n    \"prefer-scripts\": [\"error\", [\"lint\", \"test\"]]\n  }\n}\n"
  },
  {
    "path": ".npmrc",
    "content": "# npm / pnpm settings here\n\n# as a library, we want to make sure we explicitly handle peers,\n# and not rely on hidden behavior of package-managers.\nauto-install-peers=false\nstrict-peer-dependents=true\ndedupe-peer-dependents=true\nprefer-workspaces-packages=true\nresolve-peers-from-workspace-root=false\n"
  },
  {
    "path": ".prettierignore",
    "content": "node_modules/\ndist/\nlib/recommended-rules.js\nCHANGELOG.md\nREADME.md\npnpm-lock.yaml\n.github/*\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "'use strict';\n\nmodule.exports = {\n  printWidth: 100,\n  semi: true,\n  singleQuote: true,\n  trailingComma: 'es5',\n};\n"
  },
  {
    "path": ".release-plan.json",
    "content": "{\n  \"solution\": {\n    \"eslint-plugin-ember\": {\n      \"impact\": \"patch\",\n      \"oldVersion\": \"13.2.0\",\n      \"newVersion\": \"13.2.1\",\n      \"tagName\": \"latest\",\n      \"constraints\": [\n        {\n          \"impact\": \"patch\",\n          \"reason\": \"Appears in changelog section :bug: Bug Fix\"\n        },\n        {\n          \"impact\": \"patch\",\n          \"reason\": \"Appears in changelog section :house: Internal\"\n        }\n      ],\n      \"pkgJSONPath\": \"./package.json\"\n    }\n  },\n  \"description\": \"## Release (2026-04-30)\\n\\n* eslint-plugin-ember 13.2.1 (patch)\\n\\n#### :bug: Bug Fix\\n* `eslint-plugin-ember`\\n  * [#2767](https://github.com/ember-cli/eslint-plugin-ember/pull/2767) fix(require-input-label): don't count id as extra label when aria-label/labelledby present ([@johanrd](https://github.com/johanrd))\\n  * [#2772](https://github.com/ember-cli/eslint-plugin-ember/pull/2772) fix(template-no-nested-interactive): More useful error message ([@johanrd](https://github.com/johanrd))\\n\\n#### :house: Internal\\n* `eslint-plugin-ember`\\n  * [#2758](https://github.com/ember-cli/eslint-plugin-ember/pull/2758) refactor: extract landmark-roles util (preserving deliberate per-rule region exclusion) ([@johanrd](https://github.com/johanrd))\\n  * [#2753](https://github.com/ember-cli/eslint-plugin-ember/pull/2753) test(no-nested-interactive): add composite-widget hierarchy valid cases ([@johanrd](https://github.com/johanrd))\\n\\n#### Committers: 1\\n- Johan Røed ([@johanrd](https://github.com/johanrd))\\n\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Release (2026-04-30)\n\n* eslint-plugin-ember 13.2.1 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2767](https://github.com/ember-cli/eslint-plugin-ember/pull/2767) fix(require-input-label): don't count id as extra label when aria-label/labelledby present ([@johanrd](https://github.com/johanrd))\n  * [#2772](https://github.com/ember-cli/eslint-plugin-ember/pull/2772) fix(template-no-nested-interactive): More useful error message ([@johanrd](https://github.com/johanrd))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2758](https://github.com/ember-cli/eslint-plugin-ember/pull/2758) refactor: extract landmark-roles util (preserving deliberate per-rule region exclusion) ([@johanrd](https://github.com/johanrd))\n  * [#2753](https://github.com/ember-cli/eslint-plugin-ember/pull/2753) test(no-nested-interactive): add composite-widget hierarchy valid cases ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 1\n- Johan Røed ([@johanrd](https://github.com/johanrd))\n\n## Release (2026-04-28)\n\n* eslint-plugin-ember 13.2.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2763](https://github.com/ember-cli/eslint-plugin-ember/pull/2763) feat: add template-require-input-type ([@johanrd](https://github.com/johanrd))\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2766](https://github.com/ember-cli/eslint-plugin-ember/pull/2766) Update parser ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 2\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-04-27)\n\n* eslint-plugin-ember 13.1.4 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2752](https://github.com/ember-cli/eslint-plugin-ember/pull/2752) Update ember-eslint-parser to 0.11.2 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2728](https://github.com/ember-cli/eslint-plugin-ember/pull/2728) BUGFIX: template-require-mandatory-role-attributes — lowercase role + split whitespace role lists ([@johanrd](https://github.com/johanrd))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2748](https://github.com/ember-cli/eslint-plugin-ember/pull/2748) refactor: extract `html-interactive-content` util (HTML §3.2.5.2.7 authority) ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 2\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-04-25)\n\n* eslint-plugin-ember 13.1.3 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2730](https://github.com/ember-cli/eslint-plugin-ember/pull/2730) BUGFIX: template-require-valid-alt-text — reject empty-string aria-label/labelledby/alt on <input type=image>, <object>, <area> ([@johanrd](https://github.com/johanrd))\n  * [#2729](https://github.com/ember-cli/eslint-plugin-ember/pull/2729) BUGFIX: template-no-invalid-role — support DPUB/Graphics-ARIA and role-fallback lists ([@johanrd](https://github.com/johanrd))\n  * [#2726](https://github.com/ember-cli/eslint-plugin-ember/pull/2726) BUGFIX: template-no-unsupported-role-attributes — honor aria-query attribute constraints ([@johanrd](https://github.com/johanrd))\n  * [#2727](https://github.com/ember-cli/eslint-plugin-ember/pull/2727) BUGFIX: template-no-redundant-role — case-insensitive match + <select>→combobox ([@johanrd](https://github.com/johanrd))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2746](https://github.com/ember-cli/eslint-plugin-ember/pull/2746) refactor: extract isNativeElement util (fix component-vs-HTML-tag misclassification) ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 1\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-04-25)\n\n* eslint-plugin-ember 13.1.2 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2745](https://github.com/ember-cli/eslint-plugin-ember/pull/2745) chore: upgrade ember-eslint-parser from 0.11.0 to 0.11.1 ([@Copilot](https://github.com/apps/copilot-swe-agent))\n  * [#2743](https://github.com/ember-cli/eslint-plugin-ember/pull/2743) fix: template-no-autofocus-attribute — value-aware + <dialog> exception ([@johanrd](https://github.com/johanrd))\n  * [#2731](https://github.com/ember-cli/eslint-plugin-ember/pull/2731) BUGFIX: template-require-iframe-title — flag title={{null|undefined|number}} ([@johanrd](https://github.com/johanrd))\n  * [#2725](https://github.com/ember-cli/eslint-plugin-ember/pull/2725) fix(template-require-mandatory-role-attributes): use axobject-query for semantic-role exemptions ([@johanrd](https://github.com/johanrd))\n  * [#2717](https://github.com/ember-cli/eslint-plugin-ember/pull/2717) BUGFIX: template-no-empty-headings — recognize boolean aria-hidden ([@johanrd](https://github.com/johanrd))\n  * [#2723](https://github.com/ember-cli/eslint-plugin-ember/pull/2723) fix(template-no-invalid-aria-attributes): absorb allowundefined handling into validateByType ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 2\n- Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-04-25)\n\n* eslint-plugin-ember 13.1.1 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2735](https://github.com/ember-cli/eslint-plugin-ember/pull/2735) Update ember-eslint-parser to 0.11 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2736](https://github.com/ember-cli/eslint-plugin-ember/pull/2736) fix: ignore comment + whitespace nodes in template-no-yield-only ([@johanrd](https://github.com/johanrd))\n  * [#2722](https://github.com/ember-cli/eslint-plugin-ember/pull/2722) refactor(template-require-presentational-children): source role list from aria-query ([@johanrd](https://github.com/johanrd))\n  * [#2719](https://github.com/ember-cli/eslint-plugin-ember/pull/2719) BUGFIX: template-no-aria-unsupported-elements — source the reserved-element list from aria-query ([@johanrd](https://github.com/johanrd))\n  * [#2718](https://github.com/ember-cli/eslint-plugin-ember/pull/2718) BUGFIX: template-require-media-caption — compare kind=\"captions\" case-insensitively ([@johanrd](https://github.com/johanrd))\n  * [#2714](https://github.com/ember-cli/eslint-plugin-ember/pull/2714) BUGFIX: accept tabindex=\"-1\" in template-require-aria-activedescendant-tabindex ([@johanrd](https://github.com/johanrd))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2733](https://github.com/ember-cli/eslint-plugin-ember/pull/2733) test: cover {{! eslint-disable-* }} directives inside <template> in .gts ([@johanrd](https://github.com/johanrd))\n  * [#2721](https://github.com/ember-cli/eslint-plugin-ember/pull/2721) refactor(template-no-abstract-roles): source abstract-role list from aria-query ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 2\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-04-21)\n\n* eslint-plugin-ember 13.1.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2715](https://github.com/ember-cli/eslint-plugin-ember/pull/2715) feat: re-export hbs parser and document HBS flat-config setup ([@johanrd](https://github.com/johanrd))\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2713](https://github.com/ember-cli/eslint-plugin-ember/pull/2713) BUGFIX: false positive: interactive flow content inside <details> ([@johanrd](https://github.com/johanrd))\n\n#### Committers: 1\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-04-20)\n\n* eslint-plugin-ember 13.0.0 (major)\n\n#### :boom: Breaking Change\n* `eslint-plugin-ember`\n  * [#2492](https://github.com/ember-cli/eslint-plugin-ember/pull/2492) Replace deprecated ESLint context methods for ESLint 10 compatibility, dropping eslint < 8.40 ([@johanrd](https://github.com/johanrd))\n  * [#2557](https://github.com/ember-cli/eslint-plugin-ember/pull/2557) Upgrade ember-eslint-parser to 0.8.0 ([@Copilot](https://github.com/apps/copilot-swe-agent))\n  * [#2555](https://github.com/ember-cli/eslint-plugin-ember/pull/2555) Drop support for Node &lt; 20.19 ([@Copilot](https://github.com/apps/copilot-swe-agent))\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2702](https://github.com/ember-cli/eslint-plugin-ember/pull/2702) feat: add (opt-in ember-template-lint parity) template-lint-migration config  ([@johanrd](https://github.com/johanrd))\n  * [#2631](https://github.com/ember-cli/eslint-plugin-ember/pull/2631) Update ember-eslint-parser ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2562](https://github.com/ember-cli/eslint-plugin-ember/pull/2562) Extract rule: template-no-passed-in-event-handlers ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2567](https://github.com/ember-cli/eslint-plugin-ember/pull/2567) Extract rule: template-no-redundant-fn ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2570](https://github.com/ember-cli/eslint-plugin-ember/pull/2570) Extract rule: template-no-restricted-invocations ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2575](https://github.com/ember-cli/eslint-plugin-ember/pull/2575) Extract rule: template-no-this-in-template-only-components ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2583](https://github.com/ember-cli/eslint-plugin-ember/pull/2583) Extract rule: template-no-unknown-arguments-for-builtin-components ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2601](https://github.com/ember-cli/eslint-plugin-ember/pull/2601) Extract rule: template-require-context-role ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2620](https://github.com/ember-cli/eslint-plugin-ember/pull/2620) Extract rule: template-sort-invocations ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2563](https://github.com/ember-cli/eslint-plugin-ember/pull/2563) Extract rule: template-no-positional-data-test-selectors ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2564](https://github.com/ember-cli/eslint-plugin-ember/pull/2564) Extract rule: template-no-positive-tabindex ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2565](https://github.com/ember-cli/eslint-plugin-ember/pull/2565) Extract rule: template-no-potential-path-strings ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2566](https://github.com/ember-cli/eslint-plugin-ember/pull/2566) Extract rule: template-no-quoteless-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2569](https://github.com/ember-cli/eslint-plugin-ember/pull/2569) Extract rule: template-no-redundant-role ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2571](https://github.com/ember-cli/eslint-plugin-ember/pull/2571) Extract rule: template-no-route-action ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2572](https://github.com/ember-cli/eslint-plugin-ember/pull/2572) Extract rule: template-no-scope-outside-table-headings ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2573](https://github.com/ember-cli/eslint-plugin-ember/pull/2573) Extract rule: template-no-shadowed-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2574](https://github.com/ember-cli/eslint-plugin-ember/pull/2574) Extract rule: template-no-splattributes-with-class ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2576](https://github.com/ember-cli/eslint-plugin-ember/pull/2576) Extract rule: template-no-trailing-spaces ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2579](https://github.com/ember-cli/eslint-plugin-ember/pull/2579) Extract rule: template-no-triple-curlies ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2580](https://github.com/ember-cli/eslint-plugin-ember/pull/2580) Extract rule: template-no-unavailable-this ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2581](https://github.com/ember-cli/eslint-plugin-ember/pull/2581) Extract rule: template-no-unbalanced-curlies ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2582](https://github.com/ember-cli/eslint-plugin-ember/pull/2582) Extract rule: template-no-unbound ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2584](https://github.com/ember-cli/eslint-plugin-ember/pull/2584) Extract rule: template-no-unnecessary-component-helper ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2585](https://github.com/ember-cli/eslint-plugin-ember/pull/2585) Extract rule: template-no-unnecessary-concat ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2587](https://github.com/ember-cli/eslint-plugin-ember/pull/2587) Extract rule: template-no-unnecessary-curly-parens ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2588](https://github.com/ember-cli/eslint-plugin-ember/pull/2588) Extract rule: template-no-unnecessary-curly-strings ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2589](https://github.com/ember-cli/eslint-plugin-ember/pull/2589) Extract rule: template-no-unsupported-role-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2590](https://github.com/ember-cli/eslint-plugin-ember/pull/2590) Extract rule: template-no-unused-block-params ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2591](https://github.com/ember-cli/eslint-plugin-ember/pull/2591) Extract rule: template-no-valueless-arguments ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2592](https://github.com/ember-cli/eslint-plugin-ember/pull/2592) Extract rule: template-no-whitespace-for-layout ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2593](https://github.com/ember-cli/eslint-plugin-ember/pull/2593) Extract rule: template-no-whitespace-within-word ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2594](https://github.com/ember-cli/eslint-plugin-ember/pull/2594) Extract rule: template-no-with ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2595](https://github.com/ember-cli/eslint-plugin-ember/pull/2595) Extract rule: template-no-yield-block-params-to-else-inverse ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2596](https://github.com/ember-cli/eslint-plugin-ember/pull/2596) Extract rule: template-no-yield-only ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2597](https://github.com/ember-cli/eslint-plugin-ember/pull/2597) Extract rule: template-no-yield-to-default ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2598](https://github.com/ember-cli/eslint-plugin-ember/pull/2598) Extract rule: template-quotes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2599](https://github.com/ember-cli/eslint-plugin-ember/pull/2599) Extract rule: template-require-aria-activedescendant-tabindex ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2600](https://github.com/ember-cli/eslint-plugin-ember/pull/2600) Extract rule: template-require-button-type ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2602](https://github.com/ember-cli/eslint-plugin-ember/pull/2602) Extract rule: template-require-each-key ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2603](https://github.com/ember-cli/eslint-plugin-ember/pull/2603) Extract rule: template-require-form-method ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2604](https://github.com/ember-cli/eslint-plugin-ember/pull/2604) Extract rule: template-require-has-block-helper ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2605](https://github.com/ember-cli/eslint-plugin-ember/pull/2605) Extract rule: template-require-iframe-src-attribute ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2606](https://github.com/ember-cli/eslint-plugin-ember/pull/2606) Extract rule: template-require-iframe-title ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2607](https://github.com/ember-cli/eslint-plugin-ember/pull/2607) Extract rule: template-require-input-label ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2608](https://github.com/ember-cli/eslint-plugin-ember/pull/2608) Extract rule: template-require-lang-attribute ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2609](https://github.com/ember-cli/eslint-plugin-ember/pull/2609) Extract rule: template-require-mandatory-role-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2610](https://github.com/ember-cli/eslint-plugin-ember/pull/2610) Extract rule: template-require-media-caption ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2611](https://github.com/ember-cli/eslint-plugin-ember/pull/2611) Extract rule: template-require-presentational-children ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2612](https://github.com/ember-cli/eslint-plugin-ember/pull/2612) Extract rule: template-require-splattributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2613](https://github.com/ember-cli/eslint-plugin-ember/pull/2613) Extract rule: template-require-strict-mode ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2614](https://github.com/ember-cli/eslint-plugin-ember/pull/2614) Extract rule: template-require-valid-alt-text ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2625](https://github.com/ember-cli/eslint-plugin-ember/pull/2625) Extract rule: template-require-valid-form-groups ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2616](https://github.com/ember-cli/eslint-plugin-ember/pull/2616) Extract rule: template-require-valid-named-block-naming-format ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2617](https://github.com/ember-cli/eslint-plugin-ember/pull/2617) Extract rule: template-self-closing-void-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2618](https://github.com/ember-cli/eslint-plugin-ember/pull/2618) Extract rule: template-simple-modifiers ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2619](https://github.com/ember-cli/eslint-plugin-ember/pull/2619) Extract rule: template-simple-unless ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2621](https://github.com/ember-cli/eslint-plugin-ember/pull/2621) Extract rule: template-splat-attributes-only ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2622](https://github.com/ember-cli/eslint-plugin-ember/pull/2622) Extract rule: template-style-concatenation ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2623](https://github.com/ember-cli/eslint-plugin-ember/pull/2623) Extract rule: template-table-groups ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2624](https://github.com/ember-cli/eslint-plugin-ember/pull/2624) Extract rule: template-template-length ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2578](https://github.com/ember-cli/eslint-plugin-ember/pull/2578) Add native class support for order-in-controllers ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n  * [#2561](https://github.com/ember-cli/eslint-plugin-ember/pull/2561) Add native class support for order-in-routes ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n  * [#2560](https://github.com/ember-cli/eslint-plugin-ember/pull/2560) Add native class support for order-in-models and no-empty-attrs ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n  * [#2472](https://github.com/ember-cli/eslint-plugin-ember/pull/2472) Extract rule: template-no-invalid-link-text ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2551](https://github.com/ember-cli/eslint-plugin-ember/pull/2551) Add `no-tracked-built-ins` rule and ember-source version utility ([@Copilot](https://github.com/apps/copilot-swe-agent))\n  * [#2552](https://github.com/ember-cli/eslint-plugin-ember/pull/2552) Add `no-modifier-argument-destructuring` rule ([@Copilot](https://github.com/apps/copilot-swe-agent))\n  * [#2529](https://github.com/ember-cli/eslint-plugin-ember/pull/2529) Skip builtin-component-arguments in GJS/GTS when not imported from @ember/component ([@johanrd](https://github.com/johanrd))\n  * [#2413](https://github.com/ember-cli/eslint-plugin-ember/pull/2413) Extract rule: template-no-builtin-form-components ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2419](https://github.com/ember-cli/eslint-plugin-ember/pull/2419) Extract rule: template-no-duplicate-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2427](https://github.com/ember-cli/eslint-plugin-ember/pull/2427) Extract rule: template-no-duplicate-landmark-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2418](https://github.com/ember-cli/eslint-plugin-ember/pull/2418) Extract rule: template-no-down-event-binding ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2426](https://github.com/ember-cli/eslint-plugin-ember/pull/2426) Extract rule: template-no-duplicate-id ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2409](https://github.com/ember-cli/eslint-plugin-ember/pull/2409) Extract rule: template-no-bare-strings ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2452](https://github.com/ember-cli/eslint-plugin-ember/pull/2452) Extract rule: template-modifier-name-case ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2435](https://github.com/ember-cli/eslint-plugin-ember/pull/2435) Extract rule: template-no-forbidden-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2455](https://github.com/ember-cli/eslint-plugin-ember/pull/2455) Extract rule: template-no-args-paths ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2451](https://github.com/ember-cli/eslint-plugin-ember/pull/2451) Extract rule: template-deprecated-render-helper ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2456](https://github.com/ember-cli/eslint-plugin-ember/pull/2456) Extract rule: template-no-at-ember-render-modifiers ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2457](https://github.com/ember-cli/eslint-plugin-ember/pull/2457) Extract rule: template-no-bare-yield ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2460](https://github.com/ember-cli/eslint-plugin-ember/pull/2460) Extract rule: template-no-curly-component-invocation ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2450](https://github.com/ember-cli/eslint-plugin-ember/pull/2450) Extract rule: template-deprecated-inline-view-helper ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2441](https://github.com/ember-cli/eslint-plugin-ember/pull/2441) Extract rule: template-no-inline-linkto ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2438](https://github.com/ember-cli/eslint-plugin-ember/pull/2438) Extract rule: template-no-html-comments ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2461](https://github.com/ember-cli/eslint-plugin-ember/pull/2461) Extract rule: template-attribute-indentation ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2453](https://github.com/ember-cli/eslint-plugin-ember/pull/2453) Extract rule: template-no-action-on-submit-button ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2439](https://github.com/ember-cli/eslint-plugin-ember/pull/2439) Extract rule: template-no-index-component-invocation ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2462](https://github.com/ember-cli/eslint-plugin-ember/pull/2462) Extract rule: template-block-indentation ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2459](https://github.com/ember-cli/eslint-plugin-ember/pull/2459) Extract rule: template-no-class-bindings ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2464](https://github.com/ember-cli/eslint-plugin-ember/pull/2464) Extract rule: template-eol-last ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2465](https://github.com/ember-cli/eslint-plugin-ember/pull/2465) Extract rule: template-linebreak-style ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2467](https://github.com/ember-cli/eslint-plugin-ember/pull/2467) Extract rule: template-no-dynamic-subexpression-invocations ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2468](https://github.com/ember-cli/eslint-plugin-ember/pull/2468) Extract rule: template-no-extra-mut-helper-argument ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2469](https://github.com/ember-cli/eslint-plugin-ember/pull/2469) Extract rule: template-no-implicit-this ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2470](https://github.com/ember-cli/eslint-plugin-ember/pull/2470) Extract rule: template-no-input-block ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2471](https://github.com/ember-cli/eslint-plugin-ember/pull/2471) Extract rule: no-invalid-aria-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2473](https://github.com/ember-cli/eslint-plugin-ember/pull/2473) Extract rule: template-no-invalid-link-title ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2474](https://github.com/ember-cli/eslint-plugin-ember/pull/2474) Extract rule: template-no-invalid-meta ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2475](https://github.com/ember-cli/eslint-plugin-ember/pull/2475) Extract rule: template-no-invalid-role ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2477](https://github.com/ember-cli/eslint-plugin-ember/pull/2477) Extract rule: template-no-link-to-positional-params ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2479](https://github.com/ember-cli/eslint-plugin-ember/pull/2479) Extract rule: template-no-model-argument-in-route-templates ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2480](https://github.com/ember-cli/eslint-plugin-ember/pull/2480) Extract rule: template-no-multiple-empty-lines ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2481](https://github.com/ember-cli/eslint-plugin-ember/pull/2481) Extract rule: template-no-mut-helper ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2482](https://github.com/ember-cli/eslint-plugin-ember/pull/2482) Extract rule: template-no-negated-comparison ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2476](https://github.com/ember-cli/eslint-plugin-ember/pull/2476) Extract rule: template-no-jsx-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2478](https://github.com/ember-cli/eslint-plugin-ember/pull/2478) Extract rule: template-no-link-to-tagname ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2483](https://github.com/ember-cli/eslint-plugin-ember/pull/2483) Extract rule: template-no-negated-condition ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2484](https://github.com/ember-cli/eslint-plugin-ember/pull/2484) Extract rule: template-no-nested-interactive ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2485](https://github.com/ember-cli/eslint-plugin-ember/pull/2485) Extract rule: template-no-nested-landmark ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2486](https://github.com/ember-cli/eslint-plugin-ember/pull/2486) Extract rule: template-no-nested-splattributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2487](https://github.com/ember-cli/eslint-plugin-ember/pull/2487) Extract rule: template-no-obscure-array-access ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2488](https://github.com/ember-cli/eslint-plugin-ember/pull/2488) Extract rule: template-no-obsolete-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2489](https://github.com/ember-cli/eslint-plugin-ember/pull/2489) Extract rule: template-no-only-default-slot ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2490](https://github.com/ember-cli/eslint-plugin-ember/pull/2490) Extract rule: template-no-outlet-outside-routes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2491](https://github.com/ember-cli/eslint-plugin-ember/pull/2491) Extract rule: template-no-page-title-component ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2440](https://github.com/ember-cli/eslint-plugin-ember/pull/2440) Extract rule: template-no-inline-event-handlers ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2442](https://github.com/ember-cli/eslint-plugin-ember/pull/2442) Extract rule: template-no-inline-styles ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2444](https://github.com/ember-cli/eslint-plugin-ember/pull/2444) Extract rule: template-no-input-placeholder ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2447](https://github.com/ember-cli/eslint-plugin-ember/pull/2447) Extract rule: template-no-invalid-interactive ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2445](https://github.com/ember-cli/eslint-plugin-ember/pull/2445) Extract rule: template-no-input-tagname ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2448](https://github.com/ember-cli/eslint-plugin-ember/pull/2448) Add rule: ember/template-no-deprecated ([@wagenet](https://github.com/wagenet))\n  * [#1](https://github.com/ember-cli/eslint-plugin-ember/pull/1) Add base configurations ([@michalsnik](https://github.com/michalsnik))\n  * [#2430](https://github.com/ember-cli/eslint-plugin-ember/pull/2430) Extract rule: template-no-empty-headings ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2434](https://github.com/ember-cli/eslint-plugin-ember/pull/2434) Extract rule: template-no-chained-this ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2437](https://github.com/ember-cli/eslint-plugin-ember/pull/2437) Extract rule: template-no-heading-inside-button ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2429](https://github.com/ember-cli/eslint-plugin-ember/pull/2429) Extract rule: template-no-element-event-actions ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2414](https://github.com/ember-cli/eslint-plugin-ember/pull/2414) Extract rule: template-no-capital-arguments ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2407](https://github.com/ember-cli/eslint-plugin-ember/pull/2407) Extract rule: template-no-attrs-in-components ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2412](https://github.com/ember-cli/eslint-plugin-ember/pull/2412) Extract rule: template-no-block-params-for-html-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2408](https://github.com/ember-cli/eslint-plugin-ember/pull/2408) Extract rule: template-no-autofocus-attribute ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2421](https://github.com/ember-cli/eslint-plugin-ember/pull/2421) Extract rule: template-no-array-prototype-extensions ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2404](https://github.com/ember-cli/eslint-plugin-ember/pull/2404) Extract rule: template-no-aria-hidden-body ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2424](https://github.com/ember-cli/eslint-plugin-ember/pull/2424) Extract rule: template-no-action-modifiers ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2403](https://github.com/ember-cli/eslint-plugin-ember/pull/2403) Extract rule: template-no-arguments-for-html-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2405](https://github.com/ember-cli/eslint-plugin-ember/pull/2405) Extract rule: template-no-aria-unsupported-elements ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2423](https://github.com/ember-cli/eslint-plugin-ember/pull/2423) Extract rule: template-no-action ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2420](https://github.com/ember-cli/eslint-plugin-ember/pull/2420) Add strict-gjs and strict-gts configs so that users could opt in to these new rules from ember-template-lint before we do our next major ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2398](https://github.com/ember-cli/eslint-plugin-ember/pull/2398) Extract rule: template-no-abstract-roles ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2399](https://github.com/ember-cli/eslint-plugin-ember/pull/2399) Extract rule: template-no-accesskey-attribute ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2396](https://github.com/ember-cli/eslint-plugin-ember/pull/2396) Extract rule: template-link-rel-noopener ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2395](https://github.com/ember-cli/eslint-plugin-ember/pull/2395) Extract rule: template-link-href-attributes ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2391](https://github.com/ember-cli/eslint-plugin-ember/pull/2391) Extract rule: template-builtin-component-arguments ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2390](https://github.com/ember-cli/eslint-plugin-ember/pull/2390) Extract rule: template-attribute-order ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2383](https://github.com/ember-cli/eslint-plugin-ember/pull/2383) Port template-no-debugger rule from PR #2371 ([@Copilot](https://github.com/apps/copilot-swe-agent))\n  * [#2381](https://github.com/ember-cli/eslint-plugin-ember/pull/2381) Port template-no-log rule from PR #2371 ([@Copilot](https://github.com/apps/copilot-swe-agent))\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2676](https://github.com/ember-cli/eslint-plugin-ember/pull/2676) Post-merge-review: use scope manager for block-param tracking in template-no-obsolete-elements ([@johanrd](https://github.com/johanrd))\n  * [#2689](https://github.com/ember-cli/eslint-plugin-ember/pull/2689) Post-merge-review: extend allowlist with svg-tags on  template-no-block-params-for-html-elements ([@johanrd](https://github.com/johanrd))\n  * [#2662](https://github.com/ember-cli/eslint-plugin-ember/pull/2662) Post-merge-review: fix template-no-passed-in-event-handlers ignore-config format and event list ([@johanrd](https://github.com/johanrd))\n  * [#2687](https://github.com/ember-cli/eslint-plugin-ember/pull/2687) Post-merge-review: Fix template-no-arguments-for-html-elements: add svg and mathml elements ([@johanrd](https://github.com/johanrd))\n  * [#2683](https://github.com/ember-cli/eslint-plugin-ember/pull/2683) Post-merge-review: Fix template-require-lang-attribute: validate every BCP47 subtag from 'language-tags' ([@johanrd](https://github.com/johanrd))\n  * [#2694](https://github.com/ember-cli/eslint-plugin-ember/pull/2694) Post-merge-review: Fix template-no-nested-landmark: drop port-only section/region ([@johanrd](https://github.com/johanrd))\n  * [#2688](https://github.com/ember-cli/eslint-plugin-ember/pull/2688) Post-merge-review: Fix template-no-attrs-in-components: align detection with upstream ([@johanrd](https://github.com/johanrd))\n  * [#2698](https://github.com/ember-cli/eslint-plugin-ember/pull/2698) Post-merge-review: Fix template-require-form-method: throw on bad config; default enabled ([@johanrd](https://github.com/johanrd))\n  * [#2709](https://github.com/ember-cli/eslint-plugin-ember/pull/2709) remove: template-no-negated-comparison ([@johanrd](https://github.com/johanrd))\n  * [#2681](https://github.com/ember-cli/eslint-plugin-ember/pull/2681) Post-merge-review: Fix template-no-whitespace-for-layout false positive on attribute values ([@johanrd](https://github.com/johanrd))\n  * [#2693](https://github.com/ember-cli/eslint-plugin-ember/pull/2693) Post-merge-review: template-no-negated-comparison: document name-clash; drop non-standard 'ne' ([@johanrd](https://github.com/johanrd))\n  * [#2704](https://github.com/ember-cli/eslint-plugin-ember/pull/2704) chore: add originallyFrom metadata to 26 ported rules ([@johanrd](https://github.com/johanrd))\n  * [#2707](https://github.com/ember-cli/eslint-plugin-ember/pull/2707) Post-merge-review: template-no-implicit-this support regex patterns in allow option ([@johanrd](https://github.com/johanrd))\n  * [#2708](https://github.com/ember-cli/eslint-plugin-ember/pull/2708) restore: template-no-negated-condition (accidentally deleted in 133a16fc) ([@johanrd](https://github.com/johanrd))\n  * [#2652](https://github.com/ember-cli/eslint-plugin-ember/pull/2652) Post-merge-review: Fix `template-no-link-to-tagname`: only flag `@tagName`, not bare `tagName`, on angle-bracket `<LinkTo>` ([@johanrd](https://github.com/johanrd))\n  * [#2684](https://github.com/ember-cli/eslint-plugin-ember/pull/2684) Post-merge-review: Fix template-require-iframe-title: split messageIds; report each duplicate with index ([@johanrd](https://github.com/johanrd))\n  * [#2700](https://github.com/ember-cli/eslint-plugin-ember/pull/2700) Post-merge-review: Fix template-require-media-caption: skip caption check when muted is dynamic ([@johanrd](https://github.com/johanrd))\n  * [#2690](https://github.com/ember-cli/eslint-plugin-ember/pull/2690) Post-merge-review: Set templateMode: 'both' on template-no-invalid-interactive ([@johanrd](https://github.com/johanrd))\n  * [#2691](https://github.com/ember-cli/eslint-plugin-ember/pull/2691) Post-merge-review: Fix template-no-model-argument-in-route-templates: lint .gjs/.gts and unknown paths ([@johanrd](https://github.com/johanrd))\n  * [#2692](https://github.com/ember-cli/eslint-plugin-ember/pull/2692) Post-merge-review: Fix template-no-mut-helper: templateMode 'both' and unwrap setterAlternative ([@johanrd](https://github.com/johanrd))\n  * [#2695](https://github.com/ember-cli/eslint-plugin-ember/pull/2695) Post-merge-review: Fix template-no-shadowed-elements: align HTML-element detection with upstream ([@johanrd](https://github.com/johanrd))\n  * [#2685](https://github.com/ember-cli/eslint-plugin-ember/pull/2685) Post-merge-review: Add autofix to template-no-unknown-arguments-for-builtin-components: rename args and migrate events ([@johanrd](https://github.com/johanrd))\n  * [#2696](https://github.com/ember-cli/eslint-plugin-ember/pull/2696) Post-merge-review: Fix template-quotes: accept boolean root config ([@johanrd](https://github.com/johanrd))\n  * [#2697](https://github.com/ember-cli/eslint-plugin-ember/pull/2697) Post-merge-review: Fix template-require-aria-activedescendant-tabindex: autofix for non-div tags ([@johanrd](https://github.com/johanrd))\n  * [#2686](https://github.com/ember-cli/eslint-plugin-ember/pull/2686) Post-merge-review: Fix template-require-context-role: align aria-hidden scope and report oc with upstream ([@johanrd](https://github.com/johanrd))\n  * [#2699](https://github.com/ember-cli/eslint-plugin-ember/pull/2699) Post-merge-review: Fix template-require-mandatory-role-attributes: read StringLiteral .value not .original ([@johanrd](https://github.com/johanrd))\n  * [#2701](https://github.com/ember-cli/eslint-plugin-ember/pull/2701) Post-merge-review: Fix template-table-groups: align with upstream's table semantics ([@johanrd](https://github.com/johanrd))\n  * [#2664](https://github.com/ember-cli/eslint-plugin-ember/pull/2664) Post-merge-review: Fix `template-no-at-ember-render-modifiers`: detect GJS/GTS imports ([@johanrd](https://github.com/johanrd))\n  * [#2654](https://github.com/ember-cli/eslint-plugin-ember/pull/2654) Post-merge-review: Fix `template-no-action-modifiers` autofix: skip when hash pairs are present ([@johanrd](https://github.com/johanrd))\n  * [#2680](https://github.com/ember-cli/eslint-plugin-ember/pull/2680) Post-merge-review: Fix template-no-invalid-link-text: skip when link contains non-text children ([@johanrd](https://github.com/johanrd))\n  * [#2677](https://github.com/ember-cli/eslint-plugin-ember/pull/2677) Post-merge-review: Fix `template-no-action` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2675](https://github.com/ember-cli/eslint-plugin-ember/pull/2675) Post-merge-review: Fix `template-no-unnecessary-component-helper`: skip autofix for invalid GJS/GTS identifiers ([@johanrd](https://github.com/johanrd))\n  * [#2682](https://github.com/ember-cli/eslint-plugin-ember/pull/2682) Post-merge-review: Fix template-no-duplicate-landmark-elements false positive on dynamic aria-label ([@johanrd](https://github.com/johanrd))\n  * [#2660](https://github.com/ember-cli/eslint-plugin-ember/pull/2660) Post-merge-review: Fix `template-no-invalid-aria-attributes`: reject boolean strings for string-typed ARIA attributes ([@johanrd](https://github.com/johanrd))\n  * [#2659](https://github.com/ember-cli/eslint-plugin-ember/pull/2659) Post-merge-review: Fix `template-no-implicit-this`: callee detection, block-param scoping, bare `{{this}}` ([@johanrd](https://github.com/johanrd))\n  * [#2658](https://github.com/ember-cli/eslint-plugin-ember/pull/2658) Post-merge-review: Fix `template-no-dynamic-subexpression-invocations` false positive on body-position `this.*` mustaches ([@johanrd](https://github.com/johanrd))\n  * [#2657](https://github.com/ember-cli/eslint-plugin-ember/pull/2657) Post-merge-review: Fix `template-no-curly-component-invocation`: preserve `this.`/`@`/local names in suggestions, and skip JS scope bindings ([@johanrd](https://github.com/johanrd))\n  * [#2656](https://github.com/ember-cli/eslint-plugin-ember/pull/2656) Post-merge-review: Fix `template-no-chained-this` autofix: also update the closing tag ([@johanrd](https://github.com/johanrd))\n  * [#2679](https://github.com/ember-cli/eslint-plugin-ember/pull/2679) Post-merge-review: Fix `template-no-inline-linkto` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2668](https://github.com/ember-cli/eslint-plugin-ember/pull/2668) Post-merge-review: Fix `template-no-unbound` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2661](https://github.com/ember-cli/eslint-plugin-ember/pull/2661) Post-merge-review: Fix `template-no-multiple-empty-lines`: detect trailing empty lines and fix reported location ([@johanrd](https://github.com/johanrd))\n  * [#2663](https://github.com/ember-cli/eslint-plugin-ember/pull/2663) Post-merge-review: Fix `template-no-empty-headings`: recognize `<this.X>`, `<@x>`, `<ns.X>` as accessible content ([@johanrd](https://github.com/johanrd))\n  * [#2665](https://github.com/ember-cli/eslint-plugin-ember/pull/2665) Post-merge-review: Fix `template-deprecated-render-helper` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2666](https://github.com/ember-cli/eslint-plugin-ember/pull/2666) Post-merge-review: Fix `template-deprecated-inline-view-helper` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2667](https://github.com/ember-cli/eslint-plugin-ember/pull/2667) Post-merge-review: Fix `template-no-class-bindings` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2670](https://github.com/ember-cli/eslint-plugin-ember/pull/2670) Post-merge-review: Fix `template-no-log` false positive on JS scope bindings in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2671](https://github.com/ember-cli/eslint-plugin-ember/pull/2671) Post-merge-review: Fix `template-no-outlet-outside-routes` false positive on imported `outlet` ([@johanrd](https://github.com/johanrd))\n  * [#2673](https://github.com/ember-cli/eslint-plugin-ember/pull/2673) Post-merge-review: Fix `template-require-has-block-helper`: skip JS scope bindings ([@johanrd](https://github.com/johanrd))\n  * [#2674](https://github.com/ember-cli/eslint-plugin-ember/pull/2674) Post-merge-review: Fix `template-no-invalid-link-title`: track `@ember/routing` LinkTo import ([@johanrd](https://github.com/johanrd))\n  * [#2672](https://github.com/ember-cli/eslint-plugin-ember/pull/2672) Post-merge-review: Fix `template-require-input-label` mustache branch: apply strict-mode guard ([@johanrd](https://github.com/johanrd))\n  * [#2669](https://github.com/ember-cli/eslint-plugin-ember/pull/2669) Post-merge-review: Fix `template-no-input-tagname` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2678](https://github.com/ember-cli/eslint-plugin-ember/pull/2678) Post-merge-review: Fix `template-no-input-block` false positive in GJS/GTS ([@johanrd](https://github.com/johanrd))\n  * [#2651](https://github.com/ember-cli/eslint-plugin-ember/pull/2651) Post-merge-review: Fix `template-no-invalid-interactive`: align interactive element detection with upstream ([@johanrd](https://github.com/johanrd))\n  * [#2647](https://github.com/ember-cli/eslint-plugin-ember/pull/2647) Post-merge-review: Restore autofix for `template-simple-unless` ([@johanrd](https://github.com/johanrd))\n  * [#2650](https://github.com/ember-cli/eslint-plugin-ember/pull/2650) Post merge-review: Fix `template-no-quoteless-attributes` false positive on quoted values ([@johanrd](https://github.com/johanrd))\n  * [#2646](https://github.com/ember-cli/eslint-plugin-ember/pull/2646) Post-merge-review: Restore autofix for template-no-positional-data-test-selectors ([@johanrd](https://github.com/johanrd))\n  * [#2645](https://github.com/ember-cli/eslint-plugin-ember/pull/2645) Post-merge-review: Fix `template-no-this-in-template-only-components`: detect `.hbs` files with backing class on disk ([@johanrd](https://github.com/johanrd))\n  * [#2648](https://github.com/ember-cli/eslint-plugin-ember/pull/2648) Post-merge-review: Restore autofix for `template-no-redundant-fn` ([@johanrd](https://github.com/johanrd))\n  * [#2649](https://github.com/ember-cli/eslint-plugin-ember/pull/2649) Post-merge-review: Restore autofix for template-sort-invocations ([@johanrd](https://github.com/johanrd))\n  * [#2644](https://github.com/ember-cli/eslint-plugin-ember/pull/2644) Post-merge-review: Fix `template-no-unused-block-params`: detect angle-bracket block params and walk modifiers ([@johanrd](https://github.com/johanrd))\n  * [#2637](https://github.com/ember-cli/eslint-plugin-ember/pull/2637) Fix rule: ember/template-no-invalid-link-text ([@tcjr](https://github.com/tcjr))\n  * [#2636](https://github.com/ember-cli/eslint-plugin-ember/pull/2636) Fix rule: ember/template-no-unused-block-params ([@tcjr](https://github.com/tcjr))\n  * [#2633](https://github.com/ember-cli/eslint-plugin-ember/pull/2633) Remove new configs introduced in template-lint extraction ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2630](https://github.com/ember-cli/eslint-plugin-ember/pull/2630) Post-merge cleanup of eslint 10 copmpat to match with #2492 ([@johanrd](https://github.com/johanrd))\n  * [#2626](https://github.com/ember-cli/eslint-plugin-ember/pull/2626) Delete rules that never existed or have overlap with other rules ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2549](https://github.com/ember-cli/eslint-plugin-ember/pull/2549) [BUGFIX]: `no-test-import-export` false positive on non-test files ending in -test ([@johanrd](https://github.com/johanrd))\n  * [#2536](https://github.com/ember-cli/eslint-plugin-ember/pull/2536) Restore autofix: `template-block-indentation` ([@johanrd](https://github.com/johanrd))\n  * [#2523](https://github.com/ember-cli/eslint-plugin-ember/pull/2523) Post-merge review of #2461 (`template-attribute-indentation`) ([@johanrd](https://github.com/johanrd))\n  * [#2524](https://github.com/ember-cli/eslint-plugin-ember/pull/2524) Post-merge review of #2427 (`template-no-duplicate-landmark-elements`) ([@johanrd](https://github.com/johanrd))\n  * [#2525](https://github.com/ember-cli/eslint-plugin-ember/pull/2525) Post-merge review of #2450 (`template-deprecated-inline-view-helper`) ([@johanrd](https://github.com/johanrd))\n  * [#2526](https://github.com/ember-cli/eslint-plugin-ember/pull/2526) Post-merge review of #2460 (`template-no-curly-component-invocation`) ([@johanrd](https://github.com/johanrd))\n  * [#2527](https://github.com/ember-cli/eslint-plugin-ember/pull/2527) Post-merge review of #2426 (`template-no-duplicate-id`) ([@johanrd](https://github.com/johanrd))\n  * [#2528](https://github.com/ember-cli/eslint-plugin-ember/pull/2528) Skip reporting for <form method=\"dialog\"> submit buttons ([@johanrd](https://github.com/johanrd))\n  * [#2530](https://github.com/ember-cli/eslint-plugin-ember/pull/2530) Restore autofix: `template-no-action-modifiers` ([@johanrd](https://github.com/johanrd))\n  * [#2531](https://github.com/ember-cli/eslint-plugin-ember/pull/2531) Restore autofix: `template-no-array-prototype-extensions` ([@johanrd](https://github.com/johanrd))\n  * [#2532](https://github.com/ember-cli/eslint-plugin-ember/pull/2532) Restore autofix: `template-no-multiple-empty-lines` ([@johanrd](https://github.com/johanrd))\n  * [#2533](https://github.com/ember-cli/eslint-plugin-ember/pull/2533) Restore autofix: `no-negated-condition` ([@johanrd](https://github.com/johanrd))\n  * [#2534](https://github.com/ember-cli/eslint-plugin-ember/pull/2534) Restore autofix: `template-no-obscure-array-access` ([@johanrd](https://github.com/johanrd))\n  * [#2537](https://github.com/ember-cli/eslint-plugin-ember/pull/2537) Restore autofix: `template-no-curly-component-invocation` ([@johanrd](https://github.com/johanrd))\n  * [#2535](https://github.com/ember-cli/eslint-plugin-ember/pull/2535) Restore autofix: `template-attribute-order` ([@johanrd](https://github.com/johanrd))\n  * [#2538](https://github.com/ember-cli/eslint-plugin-ember/pull/2538) [BUGFIX legacy]: `avoid-leaking-state-in-ember-objects` false positives with TypeScript type assertions ([@johanrd](https://github.com/johanrd))\n  * [#2540](https://github.com/ember-cli/eslint-plugin-ember/pull/2540) [BUGFIX]: Recognize `import { service }` in order-in-* rules and `no-implicit-injections` ([@johanrd](https://github.com/johanrd))\n  * [#2541](https://github.com/ember-cli/eslint-plugin-ember/pull/2541) [BUGFIX legacy]: Broaden mirage.js `findBy` exclusion in `no-array-prototype-extensions` ([@johanrd](https://github.com/johanrd))\n  * [#2542](https://github.com/ember-cli/eslint-plugin-ember/pull/2542) [BUGFIX]: `no-tracked-properties-from-args` crash on method calls ([@johanrd](https://github.com/johanrd))\n  * [#2544](https://github.com/ember-cli/eslint-plugin-ember/pull/2544) [BUGFIX]: `no-empty-glimmer-component-classes` false positive on `declare class` ([@johanrd](https://github.com/johanrd))\n  * [#2545](https://github.com/ember-cli/eslint-plugin-ember/pull/2545) [BUGFIX]: `no-test-import-export` false positive on package imports ([@johanrd](https://github.com/johanrd))\n  * [#2539](https://github.com/ember-cli/eslint-plugin-ember/pull/2539) [BUGFIX legacy]: `require-computed-macros` self-referential autofix ([@johanrd](https://github.com/johanrd))\n  * [#2550](https://github.com/ember-cli/eslint-plugin-ember/pull/2550) [BUGFIX legacy] Fix no-implicit-injections crash with mixins and empty class bodies ([@johanrd](https://github.com/johanrd))\n  * [#2546](https://github.com/ember-cli/eslint-plugin-ember/pull/2546) [BUGFIX legacy]: `no-deprecated-router-transition-methods` crash with mixins ([@johanrd](https://github.com/johanrd))\n  * [#2548](https://github.com/ember-cli/eslint-plugin-ember/pull/2548) [BUGFIX]: `no-actions-hash` crash on TypeScript declare properties ([@johanrd](https://github.com/johanrd))\n  * [#2543](https://github.com/ember-cli/eslint-plugin-ember/pull/2543) [BUGFIX]: `no-computed-properties-in-native-classes` when file mixes native and classic classes ([@johanrd](https://github.com/johanrd))\n  * [#2508](https://github.com/ember-cli/eslint-plugin-ember/pull/2508) Fix locals tracking for deprecated-inline-view ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2496](https://github.com/ember-cli/eslint-plugin-ember/pull/2496) Post-merge review of #2408 (`template-no-autofocus-attribute`) ([@johanrd](https://github.com/johanrd))\n  * [#2500](https://github.com/ember-cli/eslint-plugin-ember/pull/2500) Post-merge review of #2424 (`template-no-action-modifiers`) ([@johanrd](https://github.com/johanrd))\n  * [#2504](https://github.com/ember-cli/eslint-plugin-ember/pull/2504) Post-merge review of #2484 (`template-no-nested-interactive`) ([@johanrd](https://github.com/johanrd))\n  * [#2501](https://github.com/ember-cli/eslint-plugin-ember/pull/2501) Post-merge review of #2471 (`template-no-invalid-aria-attributes`) ([@johanrd](https://github.com/johanrd))\n  * [#2498](https://github.com/ember-cli/eslint-plugin-ember/pull/2498) Post-merge review of #2475 (`template-no-invalid-role`) ([@johanrd](https://github.com/johanrd))\n  * [#2495](https://github.com/ember-cli/eslint-plugin-ember/pull/2495) Post-merge review of #2469 (`template-no-implicit-this`) ([@johanrd](https://github.com/johanrd))\n  * [#2497](https://github.com/ember-cli/eslint-plugin-ember/pull/2497)  Post-merge review of #2477 (`template-no-link-to-positional-params`) ([@johanrd](https://github.com/johanrd))\n  * [#2494](https://github.com/ember-cli/eslint-plugin-ember/pull/2494) Post-merge review of #2395 (`template-link-href-attributes`) ([@johanrd](https://github.com/johanrd))\n  * [#2503](https://github.com/ember-cli/eslint-plugin-ember/pull/2503) Post-merge review of #2396 (`template-link-rel-noopener`) ([@johanrd](https://github.com/johanrd))\n  * [#2499](https://github.com/ember-cli/eslint-plugin-ember/pull/2499) Post-merge review of #2414 (`template-no-capital-arguments`) ([@johanrd](https://github.com/johanrd))\n  * [#2502](https://github.com/ember-cli/eslint-plugin-ember/pull/2502) Post-merge review of #2381 (`template-no-log`) ([@johanrd](https://github.com/johanrd))\n  * [#2505](https://github.com/ember-cli/eslint-plugin-ember/pull/2505) Post-merge review of #2429 (`template-no-element-event-actions`) ([@johanrd](https://github.com/johanrd))\n  * [#2506](https://github.com/ember-cli/eslint-plugin-ember/pull/2506) Post-merge review of #2423 (`template-no-action`) ([@johanrd](https://github.com/johanrd))\n  * [#2507](https://github.com/ember-cli/eslint-plugin-ember/pull/2507) Post-merge review of #2490 (`template-no-outlet-outside-routes`) ([@johanrd](https://github.com/johanrd))\n  * [#2493](https://github.com/ember-cli/eslint-plugin-ember/pull/2493) Post-merge review of #2442 (`template-no-inline-styles`) ([@johanrd](https://github.com/johanrd))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2705](https://github.com/ember-cli/eslint-plugin-ember/pull/2705) Post-merge-review: add missing tests from ember-template-lint test suite ([@johanrd](https://github.com/johanrd))\n  * [#2703](https://github.com/ember-cli/eslint-plugin-ember/pull/2703) Post-merge-review: cleanup redundant upstream comments ([@johanrd](https://github.com/johanrd))\n  * [#2632](https://github.com/ember-cli/eslint-plugin-ember/pull/2632) Add mitata benchmark for recommended config linting ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n  * [#2520](https://github.com/ember-cli/eslint-plugin-ember/pull/2520) Revert \"Update devDependencies (major)\" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2519](https://github.com/ember-cli/eslint-plugin-ember/pull/2519) Revert \"Update devDependencies\" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2518](https://github.com/ember-cli/eslint-plugin-ember/pull/2518) Revert \"Lock file maintenance\" ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2466](https://github.com/ember-cli/eslint-plugin-ember/pull/2466) Add editor config utils ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2463](https://github.com/ember-cli/eslint-plugin-ember/pull/2463) Update the docs-generator to handle the 'templateMode: loose' in rules from nvp/port-ember-template-lint-for-gjs-gts ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2449](https://github.com/ember-cli/eslint-plugin-ember/pull/2449) Upgrade parser ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2382](https://github.com/ember-cli/eslint-plugin-ember/pull/2382) Cleanup unused snapshots ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2368](https://github.com/ember-cli/eslint-plugin-ember/pull/2368) We have prettier (and thus editors detect and run it), but what we check in CI is different ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 8\n- @NullVoxPopuli's reduced-access machine account for AI usage ([@NullVoxPopuli-ai-agent](https://github.com/NullVoxPopuli-ai-agent))\n- Copilot [Bot] ([@copilot-swe-agent](https://github.com/apps/copilot-swe-agent))\n- Michał Sajnóg ([@michalsnik](https://github.com/michalsnik))\n- Peter Wagenet ([@wagenet](https://github.com/wagenet))\n- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n- Tom Carter ([@tcjr](https://github.com/tcjr))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n- [@johanrd](https://github.com/johanrd)\n\n## Release (2026-01-22)\n\n* eslint-plugin-ember 12.7.6 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2363](https://github.com/ember-cli/eslint-plugin-ember/pull/2363) fix: classic-decorator-no-classic-methods matching private identifiers ([@c0rydoras](https://github.com/c0rydoras))\n  * [#2364](https://github.com/ember-cli/eslint-plugin-ember/pull/2364) Consider .test.{js|ts|gjs|gts} as a test file pattern ([@lego-technix](https://github.com/lego-technix))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2334](https://github.com/ember-cli/eslint-plugin-ember/pull/2334) pnpm dlx create-release-plan-setup@latest --update ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 3\n- Arthur ([@c0rydoras](https://github.com/c0rydoras))\n- LEGO Technix ([@lego-technix](https://github.com/lego-technix))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n## Release (2025-11-27)\n\neslint-plugin-ember 12.7.5 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2349](https://github.com/ember-cli/eslint-plugin-ember/pull/2349) fix: allow decorated template-tag-only classes ([@CvX](https://github.com/CvX))\n\n#### Committers: 1\n- Jarek Radosz ([@CvX](https://github.com/CvX))\n\n## Release (2025-09-14)\n\neslint-plugin-ember 12.7.4 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2331](https://github.com/ember-cli/eslint-plugin-ember/pull/2331) fix: Ignore `findBy` calls from `ember-cli-mirage` in `no-array-prototype-extensions` ([@VasylMarchuk](https://github.com/VasylMarchuk))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2332](https://github.com/ember-cli/eslint-plugin-ember/pull/2332) chore: try actions/setup-node before pnpm/action-setup ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Vasyl Marchuk ([@VasylMarchuk](https://github.com/VasylMarchuk))\n\n## Release (2025-08-22)\n\neslint-plugin-ember 12.7.3 (patch)\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2323](https://github.com/ember-cli/eslint-plugin-ember/pull/2323) chore: add npm provenance to publishing (second attempt) ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## Release (2025-08-22)\n\neslint-plugin-ember 12.7.2 (patch)\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2321](https://github.com/ember-cli/eslint-plugin-ember/pull/2321) chore: add npm provenance to publishing ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## Release (2025-08-22)\n\neslint-plugin-ember 12.7.1 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2320](https://github.com/ember-cli/eslint-plugin-ember/pull/2320) fix: `no-array-prototype-extensions` rule to lint against `setObjects()` ([@MrChocolatine](https://github.com/MrChocolatine))\n  * [#2315](https://github.com/ember-cli/eslint-plugin-ember/pull/2315) fix: no-runloop: catch namespace imports ([@Geodewd549](https://github.com/Geodewd549))\n\n#### :memo: Documentation\n* `eslint-plugin-ember`\n  * [#2313](https://github.com/ember-cli/eslint-plugin-ember/pull/2313) Remove name from CHANGELOG ([@rmachielse](https://github.com/rmachielse))\n\n#### Committers: 3\n- Richard ([@rmachielse](https://github.com/rmachielse))\n- [@Geodewd549](https://github.com/Geodewd549)\n- [@MrChocolatine](https://github.com/MrChocolatine)\n\n## Release (2025-07-24)\n\neslint-plugin-ember 12.7.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2282](https://github.com/ember-cli/eslint-plugin-ember/pull/2282) Add no-builtin-form-components rule ([@wagenet](https://github.com/wagenet))\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2308](https://github.com/ember-cli/eslint-plugin-ember/pull/2308) fix: disallow extra properties in rule options ([@andreww2012](https://github.com/andreww2012))\n\n#### Committers: 2\n- Andrew Kazakov ([@andreww2012](https://github.com/andreww2012))\n- Peter Wagenet ([@wagenet](https://github.com/wagenet))\n\n## Release (2025-07-12)\n\neslint-plugin-ember 12.6.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2306](https://github.com/ember-cli/eslint-plugin-ember/pull/2306) Add names for the eslint-inspector ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n## Release (2025-01-30)\n\neslint-plugin-ember 12.5.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2251](https://github.com/ember-cli/eslint-plugin-ember/pull/2251) Force bump the parser to latest, 0.5.9 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2252](https://github.com/ember-cli/eslint-plugin-ember/pull/2252) Prepare Release ([@github-actions[bot]](https://github.com/apps/github-actions))\n\n#### Committers: 2\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n- [@github-actions[bot]](https://github.com/apps/github-actions)\n\n## Release (2025-01-30)\n\neslint-plugin-ember 12.4.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2251](https://github.com/ember-cli/eslint-plugin-ember/pull/2251) Force bump the parser to latest, 0.5.9 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n## Release (2024-11-22)\n\neslint-plugin-ember 12.3.3 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2220](https://github.com/ember-cli/eslint-plugin-ember/pull/2220) Bump minimum parser version (fixes SVGs, MathML, custom-elements) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n## Release (2024-11-21)\n\neslint-plugin-ember 12.3.2 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2216](https://github.com/ember-cli/eslint-plugin-ember/pull/2216) upgrade parser ([@patricklx](https://github.com/patricklx))\n\n#### Committers: 1\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n## Release (2024-10-25)\n\neslint-plugin-ember 12.3.1 (patch)\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2200](https://github.com/ember-cli/eslint-plugin-ember/pull/2200) Fix no-component-lifecycle-hook in double extended classic component ([@wagenet](https://github.com/wagenet))\n\n#### Committers: 1\n- Peter Wagenet ([@wagenet](https://github.com/wagenet))\n\n## Release (2024-10-23)\n\neslint-plugin-ember 12.3.0 (minor)\n\n#### :rocket: Enhancement\n* `eslint-plugin-ember`\n  * [#2191](https://github.com/ember-cli/eslint-plugin-ember/pull/2191) Provide better gjs/gts config support for eslint 9 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :bug: Bug Fix\n* `eslint-plugin-ember`\n  * [#2195](https://github.com/ember-cli/eslint-plugin-ember/pull/2195) fix: Typo in error message ([@HeroicEric](https://github.com/HeroicEric))\n\n#### :house: Internal\n* `eslint-plugin-ember`\n  * [#2198](https://github.com/ember-cli/eslint-plugin-ember/pull/2198) Switch away from release-it to release-plan ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n  * [#2196](https://github.com/ember-cli/eslint-plugin-ember/pull/2196) Switch to pnpm + vitest (away from yarn @ 1 and jest) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 2\n- Eric Kelly ([@HeroicEric](https://github.com/HeroicEric))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n\n\n\n\n\n\n\n\n## v12.2.1 (2024-09-25)\n\n#### :bug: Bug Fix\n* [#2163](https://github.com/ember-cli/eslint-plugin-ember/pull/2163) Bump the parser ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2162](https://github.com/ember-cli/eslint-plugin-ember/pull/2162) Allow imports from `ember-data/store` ([@Windvis](https://github.com/Windvis))\n\n#### Committers: 2\n- Sam Van Campenhout ([@Windvis](https://github.com/Windvis))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n## v12.2.0 (2024-08-17)\n\n#### :rocket: Enhancement\n* [#2155](https://github.com/ember-cli/eslint-plugin-ember/pull/2155) Add new ember-data rule: `require-async-inverse-relationship` ([@wozny1989](https://github.com/wozny1989))\n* [#2157](https://github.com/ember-cli/eslint-plugin-ember/pull/2157) Consider `_test.{js|ts|gjs|gts}` as test file. ([@HEYGUL](https://github.com/HEYGUL))\n\n#### :bug: Bug Fix\n* [#2159](https://github.com/ember-cli/eslint-plugin-ember/pull/2159) Fix deprecation blocking eslint v9 ([@LucasHill](https://github.com/LucasHill))\n* [#2151](https://github.com/ember-cli/eslint-plugin-ember/pull/2151) Fix false positive error for `no-runloop` ([@mkszepp](https://github.com/mkszepp))\n\n#### :house: Internal\n* [#2153](https://github.com/ember-cli/eslint-plugin-ember/pull/2153) Fix API deprecations blocking eslint v9 compatibility ([@LucasHill](https://github.com/LucasHill))\n\n#### Committers: 4\n- Adam Woźny ([@wozny1989](https://github.com/wozny1989))\n- GUL ([@HEYGUL](https://github.com/HEYGUL))\n- Lucas Hill ([@LucasHill](https://github.com/LucasHill))\n- Markus Sanin ([@mkszepp](https://github.com/mkszepp))\n\n\n## v12.1.1 (2024-05-21)\n\n#### :bug: Bug Fix\n* [#2149](https://github.com/ember-cli/eslint-plugin-ember/pull/2149) Bump parser version ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2148](https://github.com/ember-cli/eslint-plugin-ember/pull/2148) fix test after parser update, resolves #2118 ([@patricklx](https://github.com/patricklx))\n* [#2147](https://github.com/ember-cli/eslint-plugin-ember/pull/2147) fix isTestFile - test-aware lints were not correctly identifying gjs and gts tests and test files ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n## v12.1.0 (2024-05-14)\n\n#### :rocket: Enhancement\n* [#2117](https://github.com/ember-cli/eslint-plugin-ember/pull/2117) update ember eslint parser ([@patricklx](https://github.com/patricklx))\n\n#### :bug: Bug Fix\n* [#2107](https://github.com/ember-cli/eslint-plugin-ember/pull/2107) include recommended ts-eslint rules for gts ([@patricklx](https://github.com/patricklx))\n\n#### :memo: Documentation\n* [#2142](https://github.com/ember-cli/eslint-plugin-ember/pull/2142) Fix spread operator sample in `no-array-prototype-extensions` rule doc ([@mkszepp](https://github.com/mkszepp))\n\n#### Committers: 2\n- Markus Sanin ([@mkszepp](https://github.com/mkszepp))\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n## v12.0.2 (2024-02-15)\n\n#### :bug: Bug Fix\n* [#2092](https://github.com/ember-cli/eslint-plugin-ember/pull/2092) Fix flat config for gts/gjs and `noop` parser name ([@bmish](https://github.com/bmish))\n* [#2091](https://github.com/ember-cli/eslint-plugin-ember/pull/2091) Upgrade ember-eslint-parser to 0.3.6 ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* [#2093](https://github.com/ember-cli/eslint-plugin-ember/pull/2093) Update linting ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n## v12.0.1 (2024-02-13)\n\n#### :bug: Bug Fix\n* [#2071](https://github.com/ember-cli/eslint-plugin-ember/pull/2071) Fix nested classes case in `no-ember-super-in-es-classes` ([@CvX](https://github.com/CvX))\n\n#### :memo: Documentation\n* [#2088](https://github.com/ember-cli/eslint-plugin-ember/pull/2088) add doc about gts imports in ts files ([@patricklx](https://github.com/patricklx))\n* [#2068](https://github.com/ember-cli/eslint-plugin-ember/pull/2068) add `plugin:@typescript-eslint/recommended` to readme for GTS ([@evoactivity](https://github.com/evoactivity))\n\n#### :house: Internal\n* [#2090](https://github.com/ember-cli/eslint-plugin-ember/pull/2090) Refresh lockfile ([@bmish](https://github.com/bmish))\n\n#### Committers: 4\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jarek Radosz ([@CvX](https://github.com/CvX))\n- Liam Potter ([@evoactivity](https://github.com/evoactivity))\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v12.0.0 (2024-01-13)\n\n#### :boom: Breaking Change\n* [#1962](https://github.com/ember-cli/eslint-plugin-ember/pull/1962) Drop support for Node 14, 16, 19 ([@patricklx](https://github.com/patricklx))\n* [#1963](https://github.com/ember-cli/eslint-plugin-ember/pull/1963) Add new `recommended` rules: `no-at-ember-render-modifiers`, `no-deprecated-router-transition-methods`, `no-implicit-injections`, `no-runloop`, `no-tracked-properties-from-args`, ([@patricklx](https://github.com/patricklx))\n* [#1977](https://github.com/ember-cli/eslint-plugin-ember/pull/1977) Add new `recommended` rule: `template-no-let-reference` ([@bmish](https://github.com/bmish))\n* [#1981](https://github.com/ember-cli/eslint-plugin-ember/pull/1981) Add `template-no-let-reference` rule to `recommended-gjs` and `recommended-gts` configs  ([@patricklx](https://github.com/patricklx))\n* [#1967](https://github.com/ember-cli/eslint-plugin-ember/pull/1967) Drop support for ESLint 7 ([@bmish](https://github.com/bmish))\n* [#1978](https://github.com/ember-cli/eslint-plugin-ember/pull/1978) Set config `ecmaVersion` to `2022` ([@bmish](https://github.com/bmish))\n* [#1965](https://github.com/ember-cli/eslint-plugin-ember/pull/1965) Change `useAt` option default to `true` at in `no-get` rule ([@patricklx](https://github.com/patricklx))\n* [#2028](https://github.com/ember-cli/eslint-plugin-ember/pull/2028) Move gjs/gts parser to `ember-eslint-parser` library ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :rocket: Enhancement\n* [#1939](https://github.com/ember-cli/eslint-plugin-ember/pull/1939) Add new rule `template-no-let-reference` ([@patricklx](https://github.com/patricklx))\n* [#1943](https://github.com/ember-cli/eslint-plugin-ember/pull/1943) Add new rule `template-indent` ([@patricklx](https://github.com/patricklx))\n* [#1971](https://github.com/ember-cli/eslint-plugin-ember/pull/1971) Add template block comment eslint directives ([@patricklx](https://github.com/patricklx))\n* [#1944](https://github.com/ember-cli/eslint-plugin-ember/pull/1944) Add gts/gjs configs ([@patricklx](https://github.com/patricklx))\n* [#1942](https://github.com/ember-cli/eslint-plugin-ember/pull/1942) Use custom parser for gts/gjs ([@patricklx](https://github.com/patricklx))\n* [#1975](https://github.com/ember-cli/eslint-plugin-ember/pull/1975) Update almost all dependencies ([@bmish](https://github.com/bmish))\n* [#1984](https://github.com/ember-cli/eslint-plugin-ember/pull/1984) Update `@typescript-eslint/` dependencies to v6 ([@patricklx](https://github.com/patricklx))\n* [#2020](https://github.com/ember-cli/eslint-plugin-ember/pull/2020) Support ESLint flat config ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#1994](https://github.com/ember-cli/eslint-plugin-ember/pull/1994) [gjs/gts parser] fix locations for ast after templates ([@patricklx](https://github.com/patricklx))\n* [#1992](https://github.com/ember-cli/eslint-plugin-ember/pull/1992) [gjs/gts parser] fix references for tags with a dot & non standard html tags ([@patricklx](https://github.com/patricklx))\n* [#1996](https://github.com/ember-cli/eslint-plugin-ember/pull/1996) [gjs/gts parser] fix type aware linting when using ts+gts files ([@patricklx](https://github.com/patricklx))\n* [#2005](https://github.com/ember-cli/eslint-plugin-ember/pull/2005) [gjs/gts parser] fix parsing when there are multiple default `<template>` blocks (not allowed) ([@patricklx](https://github.com/patricklx))\n* [#2055](https://github.com/ember-cli/eslint-plugin-ember/pull/2055) Bump ember-eslint-parser to `0.2.5` - Includes fix for SVG scope parsing ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2048](https://github.com/ember-cli/eslint-plugin-ember/pull/2048) Bump ember-eslint-parser to `0.2.4` ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2046](https://github.com/ember-cli/eslint-plugin-ember/pull/2046) Fix issue with `no-deprecated-router-transition-methods` throwing errors outside of class usage ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2027](https://github.com/ember-cli/eslint-plugin-ember/pull/2027) Allow ember-data type registry imports in `use-ember-data-rfc-395-imports` rule ([@wagenet](https://github.com/wagenet))\n\n#### :memo: Documentation\n* [#1969](https://github.com/ember-cli/eslint-plugin-ember/pull/1969) Add automatic rule option lists with eslint-doc-generator ([@bmish](https://github.com/bmish))\n* [#1966](https://github.com/ember-cli/eslint-plugin-ember/pull/1966) Automatically generate README configs list with eslint-doc-generator ([@bmish](https://github.com/bmish))\n* [#1980](https://github.com/ember-cli/eslint-plugin-ember/pull/1980) Improve gts gjs configuration example ([@patricklx](https://github.com/patricklx))\n* [#1990](https://github.com/ember-cli/eslint-plugin-ember/pull/1990) Fix names for `recommended-gts` / `recommended-gjs` configs in readme ([@c0rydoras](https://github.com/c0rydoras))\n\n#### :house: Internal\n* [#1974](https://github.com/ember-cli/eslint-plugin-ember/pull/1974) Update eslint-plugin-unicorn to v49 ([@bmish](https://github.com/bmish))\n* [#2018](https://github.com/ember-cli/eslint-plugin-ember/pull/2018) Switch to ESLint flat config internally ([@bmish](https://github.com/bmish))\n* [#2036](https://github.com/ember-cli/eslint-plugin-ember/pull/2036) Add `lint:fix` script ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2037](https://github.com/ember-cli/eslint-plugin-ember/pull/2037) Add volta in package.json ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 4\n- Arthur Deierlein ([@c0rydoras](https://github.com/c0rydoras))\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n## v12.0.0-alpha.4 (2023-12-22)\n\n#### :boom: Breaking Change\n* [#2028](https://github.com/ember-cli/eslint-plugin-ember/pull/2028) Move gjs/gts parser to `ember-eslint-parser` library ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :house: Internal\n* [#2036](https://github.com/ember-cli/eslint-plugin-ember/pull/2036) Add `lint:fix` script ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#2037](https://github.com/ember-cli/eslint-plugin-ember/pull/2037) Add volta in package.json ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n## v12.0.0-alpha.3 (2023-12-13)\n\n#### :rocket: Enhancement\n* [#2020](https://github.com/ember-cli/eslint-plugin-ember/pull/2020) Support ESLint flat config ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#1996](https://github.com/ember-cli/eslint-plugin-ember/pull/1996) [gjs-gts-parser] fix type aware linting when using ts+gts files ([@patricklx](https://github.com/patricklx))\n* [#2005](https://github.com/ember-cli/eslint-plugin-ember/pull/2005) [gjs-gts-parser] fix parsing when there are multiple default `<template>` blocks (not allowed) ([@patricklx](https://github.com/patricklx))\n* [#2027](https://github.com/ember-cli/eslint-plugin-ember/pull/2027) Allow ember-data type registry imports in `use-ember-data-rfc-395-imports` rule ([@wagenet](https://github.com/wagenet))\n\n#### :house: Internal\n* [#2018](https://github.com/ember-cli/eslint-plugin-ember/pull/2018) Switch to ESLint flat config internally ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v12.0.0-alpha.2 (2023-11-10)\n\n#### :bug: Bug Fix\n* [#1994](https://github.com/ember-cli/eslint-plugin-ember/pull/1994) [gjs/gts parser] fix locations for ast after templates ([@patricklx](https://github.com/patricklx))\n* [#1992](https://github.com/ember-cli/eslint-plugin-ember/pull/1992) [gjs/gts parser] fix references for tags with a dot & non standard html tags ([@patricklx](https://github.com/patricklx))\n\n#### Committers: 1\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v12.0.0-alpha.1 (2023-11-07)\n\n#### :boom: Breaking Change\n* [#1981](https://github.com/ember-cli/eslint-plugin-ember/pull/1981) Add `template-no-let-reference` rule to `recommended-gjs` and `recommended-gts` configs  ([@patricklx](https://github.com/patricklx))\n\n#### :rocket: Enhancement\n* [#1984](https://github.com/ember-cli/eslint-plugin-ember/pull/1984) Update `@typescript-eslint/` dependencies to v6 ([@patricklx](https://github.com/patricklx))\n\n#### :memo: Documentation\n* [#1980](https://github.com/ember-cli/eslint-plugin-ember/pull/1980) Improve gts gjs configuration example ([@patricklx](https://github.com/patricklx))\n* [#1990](https://github.com/ember-cli/eslint-plugin-ember/pull/1990) Fix names for `recommended-gts` / `recommended-gjs` configs in readme ([@c0rydoras](https://github.com/c0rydoras))\n\n#### Committers: 2\n- Arthur Deierlein ([@c0rydoras](https://github.com/c0rydoras))\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v12.0.0-alpha.0 (2023-11-04)\n\n#### :boom: Breaking Change\n* [#1962](https://github.com/ember-cli/eslint-plugin-ember/pull/1962) Drop support for Node 14, 16, 19 ([@patricklx](https://github.com/patricklx))\n* [#1963](https://github.com/ember-cli/eslint-plugin-ember/pull/1963) Add new `recommended` rules: `no-at-ember-render-modifiers`, `no-deprecated-router-transition-methods`, `no-implicit-injections`, `no-runloop`, `no-tracked-properties-from-args`, ([@patricklx](https://github.com/patricklx))\n* [#1977](https://github.com/ember-cli/eslint-plugin-ember/pull/1977) Add new `recommended` rule: `template-no-let-reference` ([@bmish](https://github.com/bmish))\n* [#1967](https://github.com/ember-cli/eslint-plugin-ember/pull/1967) Drop support for ESLint 7 ([@bmish](https://github.com/bmish))\n* [#1978](https://github.com/ember-cli/eslint-plugin-ember/pull/1978) Set config `ecmaVersion` to `2022` ([@bmish](https://github.com/bmish))\n* [#1965](https://github.com/ember-cli/eslint-plugin-ember/pull/1965) Change `useAt` option default to `true` at in `no-get` rule ([@patricklx](https://github.com/patricklx))\n\n#### :rocket: Enhancement\n* [#1939](https://github.com/ember-cli/eslint-plugin-ember/pull/1939) Add new rule `template-no-let-reference` ([@patricklx](https://github.com/patricklx))\n* [#1943](https://github.com/ember-cli/eslint-plugin-ember/pull/1943) Add new rule `template-indent` ([@patricklx](https://github.com/patricklx))\n* [#1971](https://github.com/ember-cli/eslint-plugin-ember/pull/1971) Add template block comment eslint directives ([@patricklx](https://github.com/patricklx))\n* [#1944](https://github.com/ember-cli/eslint-plugin-ember/pull/1944) Add gts/gjs configs ([@patricklx](https://github.com/patricklx))\n* [#1942](https://github.com/ember-cli/eslint-plugin-ember/pull/1942) Use custom parser for gts/gjs ([@patricklx](https://github.com/patricklx))\n* [#1975](https://github.com/ember-cli/eslint-plugin-ember/pull/1975) Update almost all dependencies ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1969](https://github.com/ember-cli/eslint-plugin-ember/pull/1969) Add automatic rule option lists with eslint-doc-generator ([@bmish](https://github.com/bmish))\n* [#1966](https://github.com/ember-cli/eslint-plugin-ember/pull/1966) Automatically generate README configs list with eslint-doc-generator ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1974](https://github.com/ember-cli/eslint-plugin-ember/pull/1974) Update eslint-plugin-unicorn to v49 ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v11.12.0 (2023-12-12)\n\n#### :rocket: Enhancement\n* [#2020](https://github.com/ember-cli/eslint-plugin-ember/pull/2020) Support ESLint flat config ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#2027](https://github.com/ember-cli/eslint-plugin-ember/pull/2027) Allow ember-data type registry imports in `use-ember-data-rfc-395-imports` rule ([@wagenet](https://github.com/wagenet))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Peter Wagenet ([@wagenet](https://github.com/wagenet))\n\n\n## v11.11.1 (2023-08-22)\n\n#### :bug: Bug Fix\n* [#1941](https://github.com/ember-cli/eslint-plugin-ember/pull/1941) Revert \"Use custom parser for gts/gjs (#1920)\" ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.11.0 (2023-08-21)\n\n#### :rocket: Enhancement\n* [#1920](https://github.com/ember-cli/eslint-plugin-ember/pull/1920) Use custom parser for gts/gjs ([@patricklx](https://github.com/patricklx))\n\n#### Committers: 1\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v11.10.0 (2023-07-07)\n\n#### :rocket: Enhancement\n* [#1902](https://github.com/ember-cli/eslint-plugin-ember/pull/1902) Add new rule `no-at-ember-render-modifiers` ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### Committers: 1\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n## v11.9.0 (2023-06-26)\n\n#### :rocket: Enhancement\n* [#1899](https://github.com/ember-cli/eslint-plugin-ember/pull/1899) Check template tags for service usages in `no-unused-services` ([@lin-ll](https://github.com/lin-ll))\n\n#### :house: Internal\n* [#1898](https://github.com/ember-cli/eslint-plugin-ember/pull/1898) [no-empty-glimmer-component-classes] Import the template tag value instead of hardcode ([@chrisrng](https://github.com/chrisrng))\n\n#### Committers: 2\n- Chris Ng ([@chrisrng](https://github.com/chrisrng))\n- Lucy Lin ([@lin-ll](https://github.com/lin-ll))\n\n\n## v11.8.0 (2023-05-31)\n\n#### :rocket: Enhancement\n* [#1847](https://github.com/ember-cli/eslint-plugin-ember/pull/1847) Add `useAt` option to autofix `.lastObject` to `.at(-1)` in `no-get` rule ([@ArtixZ](https://github.com/ArtixZ))\n\n#### Committers: 1\n- [@ArtixZ](https://github.com/ArtixZ)\n\n\n## v11.7.2 (2023-05-23)\n\n#### :bug: Bug Fix\n* [#1876](https://github.com/ember-cli/eslint-plugin-ember/pull/1876) Allow generic type in TypeScript class in `no-empty-glimmer-component-classes` rule ([@chrisrng](https://github.com/chrisrng))\n\n#### Committers: 7\n- Chris Ng ([@chrisrng](https://github.com/chrisrng))\n\n\n## v11.7.1 (2023-05-21)\n\n#### :bug: Bug Fix\n* [#1870](https://github.com/ember-cli/eslint-plugin-ember/pull/1870) Fix crash with default computed property import in `no-unused-services` rule ([@bmish](https://github.com/bmish))\n* [#1869](https://github.com/ember-cli/eslint-plugin-ember/pull/1869) Avoid crash with `inject` decorator in `no-restricted-service-injections` rule ([@bmish](https://github.com/bmish))\n* [#1871](https://github.com/ember-cli/eslint-plugin-ember/pull/1871) Avoid further decorator detection crashes in `no-restricted-service-injections` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.7.0 (2023-05-19)\n\n#### :rocket: Enhancement\n* [#1865](https://github.com/ember-cli/eslint-plugin-ember/pull/1865) Support autofix of numerical property access and ternary expressions in `no-get` rule ([@evanjehl](https://github.com/evanjehl))\n\n#### :bug: Bug Fix\n* [#1866](https://github.com/ember-cli/eslint-plugin-ember/pull/1866) Account for class only having template tag in `no-empty-glimmer-component-classes` rule ([@chrisrng](https://github.com/chrisrng))\n\n#### Committers: 2\n- Chris Ng ([@chrisrng](https://github.com/chrisrng))\n- [@evanjehl](https://github.com/evanjehl)\n\n\n## v11.6.0 (2023-05-16)\n\n#### :rocket: Enhancement\n* [#1853](https://github.com/ember-cli/eslint-plugin-ember/pull/1853) Support autofix in gts files ([@patricklx](https://github.com/patricklx))\n\n#### :bug: Bug Fix\n* [#1852](https://github.com/ember-cli/eslint-plugin-ember/pull/1852) Only show `no-undef` errors for templates in gts files ([@patricklx](https://github.com/patricklx))\n\n#### Committers: 1\n- Patrick Pircher ([@patricklx](https://github.com/patricklx))\n\n\n## v11.5.2 (2023-04-22)\n\n#### :bug: Bug Fix\n* [#1841](https://github.com/ember-cli/eslint-plugin-ember/pull/1841) Fix a bug in autofixer and autofix additional cases with `firstObject and `lastObject` in `no-get` rule ([@ArtixZ](https://github.com/ArtixZ))\n\n#### Committers: 1\n- [@ArtixZ](https://github.com/ArtixZ)\n\n\n## v11.5.1 (2023-04-07)\n\n#### :bug: Bug Fix\n* [#1828](https://github.com/ember-cli/eslint-plugin-ember/pull/1828) Clarify error message for `no-pause-test` rule ([@deanmarano](https://github.com/deanmarano))\n\n#### Committers: 1\n- Dean Marano ([@deanmarano](https://github.com/deanmarano))\n\n\n## v11.5.0 (2023-04-05)\n\n#### :rocket: Enhancement\n* [#1823](https://github.com/ember-cli/eslint-plugin-ember/pull/1823) Add `getProperties` autofixer to `no-get` rule ([@ArtixZ](https://github.com/ArtixZ))\n\n#### Committers: 1\n- [@ArtixZ](https://github.com/ArtixZ)\n\n\n## v11.4.9 (2023-03-28)\n\n#### :bug: Bug Fix\n* [#1819](https://github.com/ember-cli/eslint-plugin-ember/pull/1819) Bump ember-template-imports to 3.4.2 ([@hmajoros](https://github.com/hmajoros))\n\n#### Committers: 1\n- Hank Majoros ([@hmajoros](https://github.com/hmajoros))\n\n\n## v11.4.8 (2023-03-14)\n\n#### :bug: Bug Fix\n* [#1801](https://github.com/ember-cli/eslint-plugin-ember/pull/1801) Fix issue with token mapping for lint errors on template tokens in gjs/gts files by displaying eslint error on the opening `<template>` tag ([@hmajoros](https://github.com/hmajoros))\n* [#1788](https://github.com/ember-cli/eslint-plugin-ember/pull/1788) Fix `no-array-prototype extensions` undefined error from trying to access callee from non-CallExpression ([@canrozanes](https://github.com/canrozanes))\n* [#1795](https://github.com/ember-cli/eslint-plugin-ember/pull/1795) refactor glimmer post-process, better handle template tag ([@hmajoros](https://github.com/hmajoros))\n\n#### Committers: 2\n- Can Rozanes ([@canrozanes](https://github.com/canrozanes))\n- Hank Majoros ([@hmajoros](https://github.com/hmajoros))\n\n\n## v11.4.7 (2023-03-02)\n\n#### :bug: Bug Fix\n* [#1793](https://github.com/ember-cli/eslint-plugin-ember/pull/1793) [gjs] Fix bug with regex issues when parsing GLIMMER_TEMPLATE ([@hmajoros](https://github.com/hmajoros))\n* [#1792](https://github.com/ember-cli/eslint-plugin-ember/pull/1792) [gjs] Return original diagnostic if transformed line matches original line in glimmer preprocessor ([@hmajoros](https://github.com/hmajoros))\n\n#### Committers: 1\n- Hank Majoros ([@hmajoros](https://github.com/hmajoros))\n\n\n## v11.4.6 (2023-02-01)\n\n#### :bug: Bug Fix\n* [#1767](https://github.com/ember-cli/eslint-plugin-ember/pull/1767) Bump ember-template-imports to `v3.4.1` ([@gossi](https://github.com/gossi))\n\n#### Committers: 1\n- Thomas Gossmann ([@gossi](https://github.com/gossi))\n\n\n## v11.4.5 (2023-01-28)\n\n#### :bug: Bug Fix\n* [#1748](https://github.com/ember-cli/eslint-plugin-ember/pull/1748) Ignore Ember Data `store` service calls in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n* [#1761](https://github.com/ember-cli/eslint-plugin-ember/pull/1761) Fix false positive with `Promise.any()` in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.4.4 (2023-01-23)\n\n#### :bug: Bug Fix\n* [#1749](https://github.com/ember-cli/eslint-plugin-ember/pull/1749) Ignore direct instantiation of `EmberArray` in `no-array-prototype-extensions` rule ([@canrozanes](https://github.com/canrozanes))\n\n#### Committers: 1\n- Can Rozanes ([@canrozanes](https://github.com/canrozanes))\n\n\n## v11.4.3 (2023-01-15)\n\n#### :bug: Bug Fix\n* [#1735](https://github.com/ember-cli/eslint-plugin-ember/pull/1735) Fix crash from attempting to access non-existent dependent key in `no-tracked-property-from-args` rule ([@joancc](https://github.com/joancc))\n\n#### Committers: 1\n- Joan Cejudo ([@joancc](https://github.com/joancc))\n\n\n## v11.4.2 (2023-01-04)\n\n#### :bug: Bug Fix\n* [#1731](https://github.com/ember-cli/eslint-plugin-ember/pull/1731) Handle new service import style in several rules ([@wagenet](https://github.com/wagenet))\n\n#### :house: Internal\n* [#1730](https://github.com/ember-cli/eslint-plugin-ember/pull/1730) Add eslint-remote-tester ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Peter Wagenet ([@wagenet](https://github.com/wagenet))\n\n\n## v11.4.1 (2023-01-03)\n\n#### :bug: Bug Fix\n* [#1722](https://github.com/ember-cli/eslint-plugin-ember/pull/1722) Fix some crashes including with legacy classes in `no-deprecated-router-transition-methods` and `no-implicit-injections` rules ([@rtablada](https://github.com/rtablada))\n\n#### Committers: 1\n- Ryan Tablada ([@rtablada](https://github.com/rtablada))\n\n\n## v11.4.0 (2022-12-30)\n\n#### :rocket: Enhancement\n* [#1715](https://github.com/ember-cli/eslint-plugin-ember/pull/1715) Add new rule `no-deprecated-router-transition-methods` ([@rtablada](https://github.com/rtablada))\n* [#1714](https://github.com/ember-cli/eslint-plugin-ember/pull/1714) Add new rule `no-implicit-injections` ([@rtablada](https://github.com/rtablada))\n\n#### :house: Internal\n* [#1720](https://github.com/ember-cli/eslint-plugin-ember/pull/1720) Deprecate trivial node type check helpers ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Ryan Tablada ([@rtablada](https://github.com/rtablada))\n\n\n## v11.3.1 (2022-12-21)\n\n#### :bug: Bug Fix\n* [#1712](https://github.com/ember-cli/eslint-plugin-ember/pull/1712) Fix crash with `no-tracked-properties-from-args` rule ([@joancc](https://github.com/joancc))\n\n#### :house: Internal\n* [#1713](https://github.com/ember-cli/eslint-plugin-ember/pull/1713) Switch to config file for `eslint-doc-generator` ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Joan Cejudo ([@joancc](https://github.com/joancc))\n\n\n## v11.3.0 (2022-12-20)\n\n#### :rocket: Enhancement\n* [#1703](https://github.com/ember-cli/eslint-plugin-ember/pull/1703) Add new rule `no-runloop` ([@lin-ll](https://github.com/lin-ll))\n* [#1702](https://github.com/ember-cli/eslint-plugin-ember/pull/1702) Add new rule `no-tracked-properties-from-args` ([@joancc](https://github.com/joancc))\n\n#### :memo: Documentation\n* [#1693](https://github.com/ember-cli/eslint-plugin-ember/pull/1693) Mention ESLint overrides for glob patterns in `no-restricted-service-injections` rule doc ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Joan Cejudo ([@joancc](https://github.com/joancc))\n- Lucy Lin ([@lin-ll](https://github.com/lin-ll))\n\n\n## v11.2.1 (2022-11-30)\n\n#### :bug: Bug Fix\n* [#1687](https://github.com/ember-cli/eslint-plugin-ember/pull/1687) Don't lose optional chaining with `objectAt` in autofix for `no-array-prototype-extensions` rule ([@52052100](https://github.com/52052100))\n\n#### :house: Internal\n* [#1686](https://github.com/ember-cli/eslint-plugin-ember/pull/1686) Temporarily skip failing test scenario for gjs/gts processor ([@nlfurniss](https://github.com/nlfurniss))\n\n#### Committers: 2\n- Lan Yang ([@52052100](https://github.com/52052100))\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n\n\n## v11.2.0 (2022-10-27)\n\n#### :rocket: Enhancement\n* [#1395](https://github.com/ember-cli/eslint-plugin-ember/pull/1395) Support `<template>` (no-undef, etc) ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n\n#### :bug: Bug Fix\n* [#1640](https://github.com/ember-cli/eslint-plugin-ember/pull/1640) Avoid in-place sorting in `sortBy` autofixer in `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n\n#### :memo: Documentation\n* [#1646](https://github.com/ember-cli/eslint-plugin-ember/pull/1646) Automate docs with eslint-doc-generator ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Santhosh Venkata Rama Siva Thanakala Gani ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n- [@NullVoxPopuli](https://github.com/NullVoxPopuli)\n\n\n## v11.1.0 (2022-10-18)\n\n#### :rocket: Enhancement\n* [#1632](https://github.com/ember-cli/eslint-plugin-ember/pull/1632) Add `getEach` to `map` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1633](https://github.com/ember-cli/eslint-plugin-ember/pull/1633) Add `setEach` to `forEach` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1631](https://github.com/ember-cli/eslint-plugin-ember/pull/1631) Add `invoke` to `map` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1629](https://github.com/ember-cli/eslint-plugin-ember/pull/1629) Add `reject` to `filter` autofixer for no-array-prototype-extensions rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1628](https://github.com/ember-cli/eslint-plugin-ember/pull/1628) Add `objectsAt` to `map` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1630](https://github.com/ember-cli/eslint-plugin-ember/pull/1630) Add `isAny` and `isEvery` autofixers for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1627](https://github.com/ember-cli/eslint-plugin-ember/pull/1627) Add `rejectBy` to `filter` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1626](https://github.com/ember-cli/eslint-plugin-ember/pull/1626) Add `uniqBy` to `reduce` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1621](https://github.com/ember-cli/eslint-plugin-ember/pull/1621) Add `sortBy` to `sort` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1620](https://github.com/ember-cli/eslint-plugin-ember/pull/1620) Add `uniq` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1619](https://github.com/ember-cli/eslint-plugin-ember/pull/1619) Add `mapBy` to `map` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1617](https://github.com/ember-cli/eslint-plugin-ember/pull/1617) Add `without` to `filter` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1616](https://github.com/ember-cli/eslint-plugin-ember/pull/1616) Add `toArray` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1615](https://github.com/ember-cli/eslint-plugin-ember/pull/1615) Add `objectAt` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1614](https://github.com/ember-cli/eslint-plugin-ember/pull/1614) Add `findBy` to `find` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1611](https://github.com/ember-cli/eslint-plugin-ember/pull/1611) Add `compact` to `filter` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1610](https://github.com/ember-cli/eslint-plugin-ember/pull/1610) Add `filterBy` to `filter` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n* [#1609](https://github.com/ember-cli/eslint-plugin-ember/pull/1609) Add `any` to `some` autofixer for `no-array-prototype-extensions` rule ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n\n#### :bug: Bug Fix\n* [#1635](https://github.com/ember-cli/eslint-plugin-ember/pull/1635) Simpler autofix for `sortBy` with single arg for `no-array-prototype-extension` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1639](https://github.com/ember-cli/eslint-plugin-ember/pull/1639) Explain what the autofixer covers in `no-array-prototype-extensions` rule doc ([@bmish](https://github.com/bmish))\n* [#1618](https://github.com/ember-cli/eslint-plugin-ember/pull/1618) Add codemod links for jQuery-related rules ([@bmish](https://github.com/bmish))\n* [#1601](https://github.com/ember-cli/eslint-plugin-ember/pull/1601) Switch to dash for markdown lists ([@bmish](https://github.com/bmish))\n* [#1582](https://github.com/ember-cli/eslint-plugin-ember/pull/1582) Add link to deprecation RFC in `no-array-prototype-extensions` rule doc ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Santhosh Venkata Rama Siva Thanakala Gani ([@tgvrssanthosh](https://github.com/tgvrssanthosh))\n\n\n## v11.0.6 (2022-08-18)\n\n#### :bug: Bug Fix\n* [#1562](https://github.com/ember-cli/eslint-plugin-ember/pull/1562) Remove `no-array-prototype-extensions` rule from `recommended` config ([@ef4](https://github.com/ef4))\n* [#1555](https://github.com/ember-cli/eslint-plugin-ember/pull/1555) Ignore `super` in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1569](https://github.com/ember-cli/eslint-plugin-ember/pull/1569) Add link to Ember function prototype extension deprecation RFC ([@bmish](https://github.com/bmish))\n* [#1554](https://github.com/ember-cli/eslint-plugin-ember/pull/1554) Mention `no-array-prototype-extensions` ember-template-lint rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Edward Faulkner ([@ef4](https://github.com/ef4))\n\n\n## v11.0.5 (2022-08-02)\n\n#### :bug: Bug Fix\n* [#1552](https://github.com/ember-cli/eslint-plugin-ember/pull/1552) Fix false positive with `reject()` on instance of `RSVP.defer()` in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n* [#1547](https://github.com/ember-cli/eslint-plugin-ember/pull/1547) Improve false positive detection, especially for variable names containing Set/Map, in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.0.4 (2022-07-29)\n\n#### :bug: Bug Fix\n* [#1546](https://github.com/ember-cli/eslint-plugin-ember/pull/1546) Fix false positives with `RSVP.Promise.reject()` in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1545](https://github.com/ember-cli/eslint-plugin-ember/pull/1545) Improve documentation for `no-*-prototype-extensions` rules ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.0.3 (2022-07-26)\n\n#### :bug: Bug Fix\n* [#1544](https://github.com/ember-cli/eslint-plugin-ember/pull/1544) Fix false positive with Set/Map-initialized *private* class properties in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n* [#1543](https://github.com/ember-cli/eslint-plugin-ember/pull/1543) Fix false positive with Set/Map-initialized *public* class properties in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.0.2 (2022-07-23)\n\n#### :bug: Bug Fix\n* [#1538](https://github.com/ember-cli/eslint-plugin-ember/pull/1538) Fix false positive with simple Set/Map-initialized objects in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n* [#1539](https://github.com/ember-cli/eslint-plugin-ember/pull/1539) Fix false positive with `RSVP.reject()` in `no-array-prototype-extensions` ([@gilest](https://github.com/gilest))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Giles Thompson ([@gilest](https://github.com/gilest))\n\n\n## v11.0.1 (2022-07-21)\n\n#### :bug: Bug Fix\n* [#1536](https://github.com/ember-cli/eslint-plugin-ember/pull/1536) Ignore some commonly-known non-array functions/objects to reduce false positives in `no-array-prototype-extensions` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v11.0.0 (2022-07-20)\n\n#### :boom: Breaking Change\n* [#1517](https://github.com/ember-cli/eslint-plugin-ember/pull/1517) Add `no-array-prototype-extensions` as `recommended` rule (NOTE: removed as `recommended` in v11.0.6) ([@bmish](https://github.com/bmish))\n* [#1515](https://github.com/ember-cli/eslint-plugin-ember/pull/1515) Drop support for ESLint v6 ([@bmish](https://github.com/bmish))\n* [#1318](https://github.com/ember-cli/eslint-plugin-ember/pull/1318) Drop support for Node 10, 12, 15, 17 ([@aggmoulik](https://github.com/aggmoulik))\n* [#1519](https://github.com/ember-cli/eslint-plugin-ember/pull/1519) Enable `useOptionalChaining` option by default for `no-get` rule ([@bmish](https://github.com/bmish))\n* [#1518](https://github.com/ember-cli/eslint-plugin-ember/pull/1518) Remove `base` config ([@bmish](https://github.com/bmish))\n* [#1516](https://github.com/ember-cli/eslint-plugin-ember/pull/1516) Set config `ecmaVersion` to `2020` ([@bmish](https://github.com/bmish))\n* [#1513](https://github.com/ember-cli/eslint-plugin-ember/pull/1513) Stop exporting non-Ember utils ([@bmish](https://github.com/bmish))\n* [#1514](https://github.com/ember-cli/eslint-plugin-ember/pull/1514) Strictly define Node API ([@bmish](https://github.com/bmish))\n* [#1512](https://github.com/ember-cli/eslint-plugin-ember/pull/1512) Update `avoid-leaking-state-in-ember-objects` rule to augment instead of replace default config ([@bmish](https://github.com/bmish))\n\n#### :rocket: Enhancement\n* [#1529](https://github.com/ember-cli/eslint-plugin-ember/pull/1529) Better support native class property definitions (and update to ESLint v8 internally) ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1484](https://github.com/ember-cli/eslint-plugin-ember/pull/1484) Improve links in `no-array-prototype-extensions` rule doc ([@bmish](https://github.com/bmish))\n* [#1480](https://github.com/ember-cli/eslint-plugin-ember/pull/1480) Update optional rule example to use actual optional rule ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1481](https://github.com/ember-cli/eslint-plugin-ember/pull/1481) Add Node 18 to CI ([@ddzz](https://github.com/ddzz))\n* [#1352](https://github.com/ember-cli/eslint-plugin-ember/pull/1352) Upgrade ESLint dependencies and fix new linting issues ([@ddzz](https://github.com/ddzz))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Darius Dzien ([@ddzz](https://github.com/ddzz))\n- Moulik Aggarwal ([@aggmoulik](https://github.com/aggmoulik))\n\n\n## v10.6.1 (2022-05-04)\n\n#### :bug: Bug Fix\n* [#1476](https://github.com/ember-cli/eslint-plugin-ember/pull/1476) Catch `replace` in `no-array-prototype-extensions` rule ([@smilland](https://github.com/smilland))\n\n#### Committers: 1\n- Hang Li ([@smilland](https://github.com/smilland))\n\n\n## v10.6.0 (2022-04-08)\n\n#### :rocket: Enhancement\n* [#1461](https://github.com/ember-cli/eslint-plugin-ember/pull/1461) Add new rule `no-array-prototype-extensions` ([@smilland](https://github.com/smilland))\n\n#### Committers: 1\n- Hang Li ([@smilland](https://github.com/smilland))\n\n\n## v10.5.9 (2022-02-14)\n\n#### :bug: Bug Fix\n* [#1431](https://github.com/ember-cli/eslint-plugin-ember/pull/1431) Fix crash in `jquery-ember-run` rule ([@ef4](https://github.com/ef4))\n\n#### :memo: Documentation\n* [#1410](https://github.com/ember-cli/eslint-plugin-ember/pull/1410) Fix broken URLs in documentation ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1402](https://github.com/ember-cli/eslint-plugin-ember/pull/1402) Add GitHub Actions to Dependabot config ([@ddzz](https://github.com/ddzz))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Darius D. ([@ddzz](https://github.com/ddzz))\n- Edward Faulkner ([@ef4](https://github.com/ef4))\n\n\n## v10.5.8 (2021-11-23)\n\n#### :bug: Bug Fix\n* [#1374](https://github.com/ember-cli/eslint-plugin-ember/pull/1374) Allow empty-but-decorated classes in `no-empty-glimmer-component-classes` rule ([@adrigzr](https://github.com/adrigzr))\n\n#### :memo: Documentation\n* [#1364](https://github.com/ember-cli/eslint-plugin-ember/pull/1364) Fix typos in violation message for `classic-decorator-hooks` rule ([@nlfurniss](https://github.com/nlfurniss))\n\n#### Committers: 4\n- Adrián González Rus ([@adrigzr](https://github.com/adrigzr))\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Darius D. ([@ddzz](https://github.com/ddzz))\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n\n\n## v10.5.7 (2021-10-13)\n\n#### :bug: Bug Fix\n* [#1336](https://github.com/ember-cli/eslint-plugin-ember/pull/1336) Avoid crash when estraverse does not recognize node type during traversal ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v10.5.6 (2021-10-12)\n\n#### :bug: Bug Fix\n* [#1333](https://github.com/ember-cli/eslint-plugin-ember/pull/1333) Support ESLint v8 by switching from ESLint's internal traverser to `estraverse` ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v10.5.5 (2021-09-20)\n\n#### :bug: Bug Fix\n* [#1297](https://github.com/ember-cli/eslint-plugin-ember/pull/1297) Support if/else route definitions in `no-shadow-route-definition` rule ([@raido](https://github.com/raido))\n\n#### :memo: Documentation\n* [#1300](https://github.com/ember-cli/eslint-plugin-ember/pull/1300) Add `eslint-plugin` keywords in package.json ([@bmish](https://github.com/bmish))\n* [#1294](https://github.com/ember-cli/eslint-plugin-ember/pull/1294) Super call missing arguments in some rule docs ([@StephanH90](https://github.com/StephanH90))\n\n#### :house: Internal\n* [#1301](https://github.com/ember-cli/eslint-plugin-ember/pull/1301) Add jsdoc `type` annotation to rules ([@bmish](https://github.com/bmish))\n* [#1299](https://github.com/ember-cli/eslint-plugin-ember/pull/1299) Cache dependencies on GitHub Actions to speed up CI ([@ddzz](https://github.com/ddzz))\n\n#### Committers: 4\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Darius Dzien ([@ddzz](https://github.com/ddzz))\n- Raido Kuli ([@raido](https://github.com/raido))\n- [@StephanH90](https://github.com/StephanH90)\n\n\n## v10.5.4 (2021-08-24)\n\n#### :bug: Bug Fix\n* [#1286](https://github.com/ember-cli/eslint-plugin-ember/pull/1286) Avoid unnecessary optional chaining in autofix for `no-get` rule when using `useOptionalChaining` option ([@raycohen](https://github.com/raycohen))\n\n#### Committers: 1\n- Ray Cohen ([@raycohen](https://github.com/raycohen))\n\n\n## v10.5.3 (2021-08-17)\n\n#### :bug: Bug Fix\n* [#1283](https://github.com/ember-cli/eslint-plugin-ember/pull/1283) Fix crash with `let foo` in `no-controller-access-in-routes` ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v10.5.2 (2021-08-17)\n\n#### :bug: Bug Fix\n* [#1281](https://github.com/ember-cli/eslint-plugin-ember/pull/1281) Catch destructured controller access in `no-controller-access-in-routes` rule ([@bmish](https://github.com/bmish))\n* [#1277](https://github.com/ember-cli/eslint-plugin-ember/pull/1277) Fix IIFE crash in `require-return-from-computed` rule ([@aniketh-deepsource](https://github.com/aniketh-deepsource))\n\n#### :memo: Documentation\n* [#1275](https://github.com/ember-cli/eslint-plugin-ember/pull/1275) Fix typo in `no-controller-access-in-routes` rule doc ([@locks](https://github.com/locks))\n* [#1245](https://github.com/ember-cli/eslint-plugin-ember/pull/1245) Explain how to fix violations in `no-empty-glimmer-component-classes` rule doc ([@hxqlin](https://github.com/hxqlin))\n\n#### :house: Internal\n* [#1280](https://github.com/ember-cli/eslint-plugin-ember/pull/1280) Add CodeQL ([@bmish](https://github.com/bmish))\n\n#### Committers: 4\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Hannah Lin ([@hxqlin](https://github.com/hxqlin))\n- Ricardo Mendes ([@locks](https://github.com/locks))\n- [@aniketh-deepsource](https://github.com/aniketh-deepsource)\n\n\n## v10.5.1 (2021-06-20)\n\n#### :bug: Bug Fix\n* [#1237](https://github.com/ember-cli/eslint-plugin-ember/pull/1237) Stop using deprecated ESLint `report` API ([@bmish](https://github.com/bmish))\n* [#1230](https://github.com/ember-cli/eslint-plugin-ember/pull/1230) Use `meta.hasSuggestions` for suggestable rules to prepare for ESLint 8 ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1241](https://github.com/ember-cli/eslint-plugin-ember/pull/1241) Indicate which rules provide automated suggestions in README rules table ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1222](https://github.com/ember-cli/eslint-plugin-ember/pull/1222) Use `ecmaVersion` of `2020` internally for tests/linting ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v10.5.0 (2021-05-30)\n\n#### :rocket: Enhancement\n* [#1188](https://github.com/ember-cli/eslint-plugin-ember/pull/1188) Add new rule [no-implicit-service-injection-argument](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-implicit-service-injection-argument.md) ([@bmish](https://github.com/bmish))\n* [#1194](https://github.com/ember-cli/eslint-plugin-ember/pull/1194) Add new rule [no-restricted-property-modifications](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-restricted-property-modifications.md) ([@bmish](https://github.com/bmish))\n* [#1199](https://github.com/ember-cli/eslint-plugin-ember/pull/1199) build(deps): bump eslint-utils from 2.1.0 to 3.0.0 ([@dependabot[bot]](https://github.com/apps/dependabot))\n\n#### :bug: Bug Fix\n* [#1212](https://github.com/ember-cli/eslint-plugin-ember/pull/1212) Improve detection of property names (check string literals in addition to identifiers) in several rules ([@bmish](https://github.com/bmish))\n* [#1211](https://github.com/ember-cli/eslint-plugin-ember/pull/1211) Fix false positive with non-components in `require-tagless-components` rule ([@bmish](https://github.com/bmish))\n* [#1210](https://github.com/ember-cli/eslint-plugin-ember/pull/1210) Avoid some false positives with lodash usage when recognizing extended Ember objects ([@bmish](https://github.com/bmish))\n* [#1197](https://github.com/ember-cli/eslint-plugin-ember/pull/1197) Check import when detecting controller usage in `order-in-*` rules ([@lin-ll](https://github.com/lin-ll))\n* [#1196](https://github.com/ember-cli/eslint-plugin-ember/pull/1196) Check import when detecting observer usage in `order-in-*` rules ([@lin-ll](https://github.com/lin-ll))\n\n#### :memo: Documentation\n* [#1213](https://github.com/ember-cli/eslint-plugin-ember/pull/1213) Explain why some rules are not in the `recommended` config ([@bmish](https://github.com/bmish))\n* [#1204](https://github.com/ember-cli/eslint-plugin-ember/pull/1204) Improve columns in README rules table ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Lucy Lin ([@lin-ll](https://github.com/lin-ll))\n\n\n## v10.4.2 (2021-05-13)\n\n#### :bug: Bug Fix\n* [#1195](https://github.com/ember-cli/eslint-plugin-ember/pull/1195) Fix false positives with service/controller/observer detection in some rules ([@lin-ll](https://github.com/lin-ll))\n* [#1187](https://github.com/ember-cli/eslint-plugin-ember/pull/1187) Fix optional chaining support to handle newer ChainExpression implementation ([@bmish](https://github.com/bmish))\n* [#1179](https://github.com/ember-cli/eslint-plugin-ember/pull/1179) Handle spread syntax with both `babel-eslint` and `@babel/eslint-parser` parsers in `order-in-*` rules ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1191](https://github.com/ember-cli/eslint-plugin-ember/pull/1191) Use `requireindex` to export rules and configs ([@bmish](https://github.com/bmish))\n* [#1180](https://github.com/ember-cli/eslint-plugin-ember/pull/1180) Switch from `babel-eslint` to `@babel/eslint-parser` ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Lucy Lin ([@lin-ll](https://github.com/lin-ll))\n\n\n## v10.4.1 (2021-04-21)\n\n#### :bug: Bug Fix\n* [#1160](https://github.com/ember-cli/eslint-plugin-ember/pull/1160) Account for observer dependent keys in `no-unused-services` rule ([@lin-ll](https://github.com/lin-ll))\n* [#1164](https://github.com/ember-cli/eslint-plugin-ember/pull/1164) Account for `observes` decorator in `no-unused-services` rule ([@lin-ll](https://github.com/lin-ll))\n* [#1162](https://github.com/ember-cli/eslint-plugin-ember/pull/1162) Update several rules to check imports when checking for Ember service injections ([@lin-ll](https://github.com/lin-ll))\n* [#1167](https://github.com/ember-cli/eslint-plugin-ember/pull/1167) Update route rules to handle route path option passed as object variable ([@bmish](https://github.com/bmish))\n* [#1165](https://github.com/ember-cli/eslint-plugin-ember/pull/1165) Improve robustness of classic class body detection in several rules using `getModuleProperties` util ([@bmish](https://github.com/bmish))\n* [#1159](https://github.com/ember-cli/eslint-plugin-ember/pull/1159) Improve robustness of classic class component body detection in `require-tagless-components` rule ([@bmish](https://github.com/bmish))\n* [#1158](https://github.com/ember-cli/eslint-plugin-ember/pull/1158) Improve robustness of classic class controller body detection in `no-controllers` rule ([@bmish](https://github.com/bmish))\n* [#1168](https://github.com/ember-cli/eslint-plugin-ember/pull/1168) Avoid some false positives with jQuery usage when recognizing extended objects ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1161](https://github.com/ember-cli/eslint-plugin-ember/pull/1161) Tweak messaging around false positives in `no-unused-services` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Lucy Lin ([@lin-ll](https://github.com/lin-ll))\n\n\n## v10.4.0 (2021-04-20)\n\n#### :rocket: Enhancement\n* [#1143](https://github.com/ember-cli/eslint-plugin-ember/pull/1143) Add new rule `no-unused-services` ([@lin-ll](https://github.com/lin-ll))\n* [#1127](https://github.com/ember-cli/eslint-plugin-ember/pull/1127) Add automated suggestion to `route-path-style` rule for converting route path to kebab case ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#1150](https://github.com/ember-cli/eslint-plugin-ember/pull/1150) `no-get` rule should ignore proxy classes that look like `ObjectProxy.extend(SomeMixin)` ([@bmish](https://github.com/bmish))\n* [#1149](https://github.com/ember-cli/eslint-plugin-ember/pull/1149) Detect classic classes which have object variables passed to them in `no-classic-classes` rule ([@bmish](https://github.com/bmish))\n* [#1135](https://github.com/ember-cli/eslint-plugin-ember/pull/1135) Fix false positive in same level routes but nested paths in `no-shadow-route-definition` rule ([@raido](https://github.com/raido))\n* [#1132](https://github.com/ember-cli/eslint-plugin-ember/pull/1132) Fix crash with dynamic/variable route name in `no-shadow-route-definition` rule (again) ([@raido](https://github.com/raido))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Lucy Lin ([@lin-ll](https://github.com/lin-ll))\n- Raido Kuli ([@raido](https://github.com/raido))\n\n\n## v10.3.0 (2021-03-22)\n\n#### :rocket: Enhancement\n* [#1113](https://github.com/ember-cli/eslint-plugin-ember/pull/1113) Add `additionalClassImports` option to `no-classic-classes` rule ([@scalvert](https://github.com/scalvert))\n\n#### :bug: Bug Fix\n* [#1115](https://github.com/ember-cli/eslint-plugin-ember/pull/1115) Fix crash with dynamic/variable route name in `no-shadow-route-definition` rule ([@bmish](https://github.com/bmish))\n* [#1102](https://github.com/ember-cli/eslint-plugin-ember/pull/1102) Fix crash with `this.extend()` in `no-classic-classes` rule ([@bmish](https://github.com/bmish))\n* [#1114](https://github.com/ember-cli/eslint-plugin-ember/pull/1114) Ensure rules validate arrays in options to have at least one item and unique items ([@bmish](https://github.com/bmish))\n* [#1103](https://github.com/ember-cli/eslint-plugin-ember/pull/1103) Only calculate source module name once in import util function for slight optimization ([@bmish](https://github.com/bmish))\n* [#1081](https://github.com/ember-cli/eslint-plugin-ember/pull/1081) Update `avoid-leaking-state-in-ember-objects` rule to apply to mixins ([@jaydgruber](https://github.com/jaydgruber))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Steve Calvert ([@scalvert](https://github.com/scalvert))\n- [@jaydgruber](https://github.com/jaydgruber)\n\n\n## v10.2.0 (2021-01-31)\n\n#### :rocket: Enhancement\n* [#1079](https://github.com/ember-cli/eslint-plugin-ember/pull/1079) Add new rule [no-html-safe](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-html-safe.md) ([@patocallaghan](https://github.com/patocallaghan))\n\n#### :bug: Bug Fix\n* [#1072](https://github.com/ember-cli/eslint-plugin-ember/pull/1072) Improve jquery detection in `jquery-ember-run` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Pat O'Callaghan ([@patocallaghan](https://github.com/patocallaghan))\n\n\n## v10.1.2 (2021-01-11)\n\n#### :bug: Bug Fix\n* [#1063](https://github.com/ember-cli/eslint-plugin-ember/pull/1063) Improve detection of globals and catch additional jQuery function calls in `no-jquery` rule ([@BarryThePenguin](https://github.com/BarryThePenguin))\n* [#1066](https://github.com/ember-cli/eslint-plugin-ember/pull/1066) Improve detection of globals in `no-global-jquery` rule ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1069](https://github.com/ember-cli/eslint-plugin-ember/pull/1069) Improve tests for `jquery-ember-run` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jonathan Haines ([@BarryThePenguin](https://github.com/BarryThePenguin))\n\n\n## v10.1.1 (2020-12-29)\n\n#### :bug: Bug Fix\n* [#1059](https://github.com/ember-cli/eslint-plugin-ember/pull/1059) Do not warn about Glimmer lifecycle hooks on classic components in `no-component-lifecycle-hooks` rule ([@Turbo87](https://github.com/Turbo87))\n\n#### :house: Internal\n* [#1060](https://github.com/ember-cli/eslint-plugin-ember/pull/1060) Automate release process with release-it-lerna-changelog ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n\n\n## v10.1.0 (2020-12-28)\n\n#### :rocket: Enhancement\n* [#1056](https://github.com/ember-cli/eslint-plugin-ember/pull/1056) Add new rule [no-current-route-name](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-current-route-name.md) ([@Turbo87](https://github.com/Turbo87))\n* [#1055](https://github.com/ember-cli/eslint-plugin-ember/pull/1055) Add new rule [require-fetch-import](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-fetch-import.md) ([@Turbo87](https://github.com/Turbo87))\n\n#### :bug: Bug Fix\n* [#1054](https://github.com/ember-cli/eslint-plugin-ember/pull/1054) Handle commas inside quotes in selectors in [require-valid-css-selector-in-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-valid-css-selector-in-test-helpers.md) rule ([@bmish](https://github.com/bmish))\n* [#1051](https://github.com/ember-cli/eslint-plugin-ember/pull/1051) Fix path checks for Windows in [no-test-import-export](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-import-export.md) and [no-test-support-import](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-support-import.md) rules ([@dwickern](https://github.com/dwickern))\n* [#1038](https://github.com/ember-cli/eslint-plugin-ember/pull/1038) Improve detection of global window methods in [prefer-ember-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/prefer-ember-test-helpers.md) rule ([@bmish](https://github.com/bmish))\n* [#1043](https://github.com/ember-cli/eslint-plugin-ember/pull/1043) Fix false positive in [no-shadow-route-definition](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-shadow-route-definition.md) rule ([@raido](https://github.com/raido))\n* [#1040](https://github.com/ember-cli/eslint-plugin-ember/pull/1040) `willDestroy` should be considered a classic component lifecycle hook in [require-super-in-lifecycle-hooks](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) and [no-component-lifecycle-hooks](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-component-lifecycle-hooks.md) rules ([@bmish](https://github.com/bmish))\n* [#1036](https://github.com/ember-cli/eslint-plugin-ember/pull/1036) Catch `willDestroy` Glimmer component hook in [require-super-in-lifecycle-hooks](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#1052](https://github.com/ember-cli/eslint-plugin-ember/pull/1052) Add Windows to CI testing matrix ([@dwickern](https://github.com/dwickern))\n\n#### Committers: 4\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Derek Wickern ([@dwickern](https://github.com/dwickern))\n- Raido Kuli ([@raido](https://github.com/raido))\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n\n## v10.0.2 (2020-12-04)\n\n#### :bug: Bug Fix\n* [#1029](https://github.com/ember-cli/eslint-plugin-ember/pull/1029) Handle comma-separated selectors in [require-valid-css-selector-in-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-valid-css-selector-in-test-helpers.md) rule ([@bmish](https://github.com/bmish))\n* [#1030](https://github.com/ember-cli/eslint-plugin-ember/pull/1030) Allow using string functions directly from Ember in [no-string-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-string-prototype-extensions.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1028](https://github.com/ember-cli/eslint-plugin-ember/pull/1028) Mention Ember 3.13 minimum version to use tracked properties and Glimmer components in some rule docs ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v10.0.1 (2020-12-02)\n\n#### :bug: Bug Fix\n* [#1027](https://github.com/ember-cli/eslint-plugin-ember/pull/1027) Fix crash when non-expression precedes `settled()` in [no-settled-after-test-helper](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-settled-after-test-helper.md) rule ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 1\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n\n## v10.0.0 (2020-12-01)\n\n#### :boom: Breaking Change\n* [#1025](https://github.com/ember-cli/eslint-plugin-ember/pull/1025) Promote `octane` rules to `recommended` config and delete `octane` config ([@bmish](https://github.com/bmish))\n  * [classic-decorator-hooks](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/classic-decorator-hooks.md)\n  * [classic-decorator-no-classic-methods](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/classic-decorator-no-classic-methods.md)\n  * [no-actions-hash](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-actions-hash.md)\n  * [no-classic-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-classic-classes.md)\n  * [no-classic-components](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-classic-components.md)\n  * [no-component-lifecycle-hooks](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-component-lifecycle-hooks.md)\n  * [no-computed-properties-in-native-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-computed-properties-in-native-classes.md)\n  * [require-tagless-components](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-tagless-components.md)\n* [#1024](https://github.com/ember-cli/eslint-plugin-ember/pull/1024) Enable additional `recommended` rules ([@bmish](https://github.com/bmish))\n  * [no-empty-glimmer-component-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-empty-glimmer-component-classes.md)\n  * [no-settled-after-test-helper](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-settled-after-test-helper.md)\n  * [no-shadow-route-definition](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-shadow-route-definition.md)\n  * [no-string-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-string-prototype-extensions.md)\n  * [no-test-support-import](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-support-import.md)\n  * [no-try-invoke](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-try-invoke.md)\n  * [require-valid-css-selector-in-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-valid-css-selector-in-test-helpers.md)\n* [#1021](https://github.com/ember-cli/eslint-plugin-ember/pull/1021) Update `checkPlainGetters` option default to true in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n* [#1019](https://github.com/ember-cli/eslint-plugin-ember/pull/1019) Update `checkNativeClasses` option default to true in [require-super-in-init](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule ([@bmish](https://github.com/bmish))\n* [#1018](https://github.com/ember-cli/eslint-plugin-ember/pull/1018) Update `checkInitOnly` option default to false in [require-super-in-init](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule ([@bmish](https://github.com/bmish))\n* [#1020](https://github.com/ember-cli/eslint-plugin-ember/pull/1020) Rename `require-super-in-init` rule to [require-super-in-lifecycle-hooks](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) ([@bmish](https://github.com/bmish))\n* [#1022](https://github.com/ember-cli/eslint-plugin-ember/pull/1022) Update `checkSafeObjects` option default to true in [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) rule ([@bmish](https://github.com/bmish))\n* [#1023](https://github.com/ember-cli/eslint-plugin-ember/pull/1023) Update `checkUnsafeObjects` option default to true in [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#1006](https://github.com/ember-cli/eslint-plugin-ember/pull/1006) Elaborate on configuration in [avoid-leaking-state-in-ember-objects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/avoid-leaking-state-in-ember-objects.md) rule doc ([@cincodenada](https://github.com/cincodenada))\n* [#1005](https://github.com/ember-cli/eslint-plugin-ember/pull/1005) Fix typo in [require-valid-css-selector-in-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-valid-css-selector-in-test-helpers.md) rule doc ([@jsturgis](https://github.com/jsturgis))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jeff Sturgis ([@jsturgis](https://github.com/jsturgis))\n- Joel Bradshaw ([@cincodenada](https://github.com/cincodenada))\n\n## v9.6.0 (2020-11-09)\n\n#### :rocket: Enhancement\n* [#1000](https://github.com/ember-cli/eslint-plugin-ember/pull/1000) Add `catchSafeObjects` and `catchUnsafeObjects` options (default false) to [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#1001](https://github.com/ember-cli/eslint-plugin-ember/pull/1001) Fix issues with [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule autofix with array access in nested path ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v9.5.0 (2020-11-05)\n\n#### :rocket: Enhancement\n* [#993](https://github.com/ember-cli/eslint-plugin-ember/pull/993) Add new rule [no-empty-glimmer-component-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-empty-glimmer-component-classes.md) ([@hxqlin](https://github.com/hxqlin))\n\n#### :bug: Bug Fix\n* [#998](https://github.com/ember-cli/eslint-plugin-ember/pull/998) Fix autofix for array element access at beginning of path string in `no-get` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Hannah Lin ([@hxqlin](https://github.com/hxqlin))\n\n## v9.4.0 (2020-10-28)\n\n#### :rocket: Enhancement\n* [#985](https://github.com/ember-cli/eslint-plugin-ember/pull/985) Add new rule [no-settled-after-test-helper](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-settled-after-test-helper.md) ([@Turbo87](https://github.com/Turbo87))\n* [#978](https://github.com/ember-cli/eslint-plugin-ember/pull/978) Add new rule [no-shadow-route-definition](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-shadow-route-definition.md) ([@raido](https://github.com/raido))\n* [#986](https://github.com/ember-cli/eslint-plugin-ember/pull/986) Add new rule [no-string-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-string-prototype-extensions.md) ([@Turbo87](https://github.com/Turbo87))\n* [#980](https://github.com/ember-cli/eslint-plugin-ember/pull/980) Add new rule [require-valid-css-selector-in-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-valid-css-selector-in-test-helpers.md) ([@jsturgis](https://github.com/jsturgis))\n\n#### :house: Internal\n* [#991](https://github.com/ember-cli/eslint-plugin-ember/pull/991) Add test to ensure rule test files have correct test suite name ([@bmish](https://github.com/bmish))\n\n#### Committers: 4\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jeff Sturgis ([@jsturgis](https://github.com/jsturgis))\n- Raido Kuli ([@raido](https://github.com/raido))\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n\n## v9.3.0 (2020-10-08)\n\n#### :rocket: Enhancement\n* [#973](https://github.com/ember-cli/eslint-plugin-ember/pull/973) Add new rule [no-try-invoke](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-try-invoke.md) ([@bachvo](https://github.com/bachvo))\n\n#### :bug: Bug Fix\n* [#971](https://github.com/ember-cli/eslint-plugin-ember/pull/971) Handle path separators for different platforms in [no-test-support-import](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-support-import.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- bach vo ([@bachvo](https://github.com/bachvo))\n\n## v9.2.0 (2020-10-02)\n\n#### :rocket: Enhancement\n* [#966](https://github.com/ember-cli/eslint-plugin-ember/pull/966) Add new rule [no-test-support-import](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-support-import.md) ([@gabrielcsapo](https://github.com/gabrielcsapo))\n\n#### :bug: Bug Fix\n* [#967](https://github.com/ember-cli/eslint-plugin-ember/pull/967) Avoid false positives and properly check imports in [no-observers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-observers.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Gabriel Csapo ([@gabrielcsapo](https://github.com/gabrielcsapo))\n\n## v9.1.1 (2020-09-27)\n\n#### :bug: Bug Fix\n* [#962](https://github.com/ember-cli/eslint-plugin-ember/pull/962) Do not pass `...arguments` in autofix for attrs hooks in [require-super-in-init](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v9.1.0 (2020-09-27)\n\n#### :rocket: Enhancement\n* [#961](https://github.com/ember-cli/eslint-plugin-ember/pull/961) Add `checkPlainGetters` option (default false) to [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n* [#957](https://github.com/ember-cli/eslint-plugin-ember/pull/957) Add `checkInitOnly` (default true) and `checkNativeClasses` (default false) options to [require-super-in-init](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule ([@bmish](https://github.com/bmish))\n* [#950](https://github.com/ember-cli/eslint-plugin-ember/pull/950) Add autofixer to [require-super-in-init](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#956](https://github.com/ember-cli/eslint-plugin-ember/pull/956) Add imports in [no-test-module-for](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-module-for.md) rule doc ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#945](https://github.com/ember-cli/eslint-plugin-ember/pull/945) Add sort-package-json ([@bmish](https://github.com/bmish))\n* [#944](https://github.com/ember-cli/eslint-plugin-ember/pull/944) Ensure rule doc notices are present in the correct order ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v9.0.0 (2020-09-07)\n\n#### :boom: Breaking Change\n* [#940](https://github.com/ember-cli/eslint-plugin-ember/pull/940) Enable additional [recommended](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/recommended-rules.js) rules ([@bmish](https://github.com/bmish))\n  * [no-assignment-of-untracked-properties-used-in-tracking-contexts](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md)\n  * [no-controller-access-in-routes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-controller-access-in-routes.md)\n  * [no-invalid-test-waiters](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-test-waiters.md)\n  * [no-noop-setup-on-error-in-before](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-noop-setup-on-error-in-before.md)\n  * [no-test-this-render](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-this-render.md)\n  * [prefer-ember-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/prefer-ember-test-helpers.md)\n* [#943](https://github.com/ember-cli/eslint-plugin-ember/pull/943) Enable `catchEvents` option in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n* [#942](https://github.com/ember-cli/eslint-plugin-ember/pull/942) Enable `catchSafeObjects` option in [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule ([@bmish](https://github.com/bmish))\n* [#941](https://github.com/ember-cli/eslint-plugin-ember/pull/941) Enable `catchRouterMicrolib` and `catchRouterMain` options in [no-private-routing-service](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-private-routing-service.md) rule ([@bmish](https://github.com/bmish))\n* [#939](https://github.com/ember-cli/eslint-plugin-ember/pull/939) Drop ESLint 5 support and add peer dependency on ESLint 6+ ([@bmish](https://github.com/bmish))\n* [#938](https://github.com/ember-cli/eslint-plugin-ember/pull/938) Drop Node 13 support ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.14.0 (2020-09-07)\n\n#### :rocket: Enhancement\n* [#934](https://github.com/ember-cli/eslint-plugin-ember/pull/934) Add support and enforcement for spread syntax in `order-in-*` rules ([@bmish](https://github.com/bmish))\n* [#928](https://github.com/ember-cli/eslint-plugin-ember/pull/928) Refactor [require-super-in-init](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-super-in-lifecycle-hooks.md) rule to improve performance ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#933](https://github.com/ember-cli/eslint-plugin-ember/pull/933) Fix spread syntax crash in [routes-segments-snake-case](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/routes-segments-snake-case.md) rule ([@bmish](https://github.com/bmish))\n* [#932](https://github.com/ember-cli/eslint-plugin-ember/pull/932) Fix spread syntax crash in [route-path-style](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/route-path-style.md) rule ([@bmish](https://github.com/bmish))\n* [#930](https://github.com/ember-cli/eslint-plugin-ember/pull/930) Fix spread syntax crash in [no-restricted-resolver-tests](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-restricted-resolver-tests.md) rule ([@bmish](https://github.com/bmish))\n* [#931](https://github.com/ember-cli/eslint-plugin-ember/pull/931) Fix spread syntax crash in [no-unnecessary-route-path-option](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-unnecessary-route-path-option.md) rule ([@bmish](https://github.com/bmish))\n* [#929](https://github.com/ember-cli/eslint-plugin-ember/pull/929) Fix spread syntax crash in [avoid-using-needs-in-controllers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/avoid-using-needs-in-controllers.md) rule ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#935](https://github.com/ember-cli/eslint-plugin-ember/pull/935) Add some more spread syntax tests ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.13.0 (2020-08-26)\n\n#### :rocket: Enhancement\n* [#920](https://github.com/ember-cli/eslint-plugin-ember/pull/920) Add new rule [no-noop-setup-on-error-in-before](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-noop-setup-on-error-in-before.md) ([@v-korshun](https://github.com/v-korshun))\n\n#### :bug: Bug Fix\n* [#923](https://github.com/ember-cli/eslint-plugin-ember/pull/923) Fix crash with spread syntax in [no-actions-hash](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-actions-hash.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Viktar ([@v-korshun](https://github.com/v-korshun))\n\n## v8.12.0 (2020-08-18)\n\n#### :rocket: Enhancement\n* [#916](https://github.com/ember-cli/eslint-plugin-ember/pull/916) Add `catchEvents` option (default false) to [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#914](https://github.com/ember-cli/eslint-plugin-ember/pull/914) Improve `set()` detection logic in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule to avoid false positives, catch missed cases, and check imports ([@bmish](https://github.com/bmish))\n* [#919](https://github.com/ember-cli/eslint-plugin-ember/pull/919) Fix crash with variable path in [route-path-style](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/route-path-style.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.11.0 (2020-08-14)\n\n#### :rocket: Enhancement\n* [#912](https://github.com/ember-cli/eslint-plugin-ember/pull/912) Add `catchSafeObjects` option (default false) to [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule to catch `get(foo, 'bar')` ([@bmish](https://github.com/bmish))\n* [#913](https://github.com/ember-cli/eslint-plugin-ember/pull/913) Add `catchUnsafeObjects` option (default false) to [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule to catch `foo.get('bar')` ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#911](https://github.com/ember-cli/eslint-plugin-ember/pull/911) Update [no-test-import-export](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-import-export.md) rule to allow importing from anything under `tests/helpers` path (when using relative path) ([@bmish](https://github.com/bmish))\n* [#909](https://github.com/ember-cli/eslint-plugin-ember/pull/909) Check imports when detecting computed properties in many rules ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n\n## v8.10.1 (2020-08-07)\n\n#### :bug: Bug Fix\n* [#908](https://github.com/ember-cli/eslint-plugin-ember/pull/908) Check imported `get`/`getProperties`/`getWithDefault` functions for missing dependencies in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n* [#907](https://github.com/ember-cli/eslint-plugin-ember/pull/907) Check imports in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n* [#906](https://github.com/ember-cli/eslint-plugin-ember/pull/906) Avoid crash from classes extending a non-identifier superclass during Ember core module check ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.10.0 (2020-08-05)\n\n#### :rocket: Enhancement\n* [#898](https://github.com/ember-cli/eslint-plugin-ember/pull/898) Add new rule [no-controller-access-in-routes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-controller-access-in-routes.md) ([@emonroy](https://github.com/emonroy))\n* [#887](https://github.com/ember-cli/eslint-plugin-ember/pull/887) Add option for custom computed property macros in [no-assignment-of-untracked-properties-used-in-tracking-contexts](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md) rule ([@mongoose700](https://github.com/mongoose700))\n\n#### Committers: 2\n- Eduardo Monroy Martínez ([@emonroy](https://github.com/emonroy))\n- Michael Peirce ([@mongoose700](https://github.com/mongoose700))\n\n## v8.9.2 (2020-07-23)\n\n#### :bug: Bug Fix\n* [#895](https://github.com/ember-cli/eslint-plugin-ember/pull/895) Update [no-test-import-export](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-import-export.md) rule to allow importing from anything under `tests/helpers` path ([@bmish](https://github.com/bmish))\n* [#894](https://github.com/ember-cli/eslint-plugin-ember/pull/894) Ensure [no-attrs-in-components](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-attrs-in-components.md) rule only runs inside components ([@bmish](https://github.com/bmish))\n* [#893](https://github.com/ember-cli/eslint-plugin-ember/pull/893) Support array element access in [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule autofix ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#888](https://github.com/ember-cli/eslint-plugin-ember/pull/888) Add npm-package-json-lint ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.9.1 (2020-07-05)\n\n#### :bug: Bug Fix\n* [#883](https://github.com/ember-cli/eslint-plugin-ember/pull/883) Gather dependent keys from computed property macros in [no-assignment-of-untracked-properties-used-in-tracking-contexts](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md) rule ([@bmish](https://github.com/bmish))\n* [#880](https://github.com/ember-cli/eslint-plugin-ember/pull/880) Check imports in [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule ([@bmish](https://github.com/bmish))\n* [#881](https://github.com/ember-cli/eslint-plugin-ember/pull/881) Check imports in [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) rule ([@bmish](https://github.com/bmish))\n* [#882](https://github.com/ember-cli/eslint-plugin-ember/pull/882) Check imports in [no-pause-test](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-pause-test.md) rule ([@bmish](https://github.com/bmish))\n* [#879](https://github.com/ember-cli/eslint-plugin-ember/pull/879) Autofix nested paths in the left side of an assignment without using optional chaining in the [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.9.0 (2020-06-28)\n\n#### :rocket: Enhancement\n* [#871](https://github.com/ember-cli/eslint-plugin-ember/pull/871) Add `catchRouterMain` option (default false) to [no-private-routing-service](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-private-routing-service.md) rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#870](https://github.com/ember-cli/eslint-plugin-ember/pull/870) Fix false positive involving `this` keyword with `filterBy` / `mapBy` in [require-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-macros.md) rule ([@bmish](https://github.com/bmish))\n* [#868](https://github.com/ember-cli/eslint-plugin-ember/pull/868) Fix false negatives in [no-invalid-test-waiters](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-test-waiters.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.8.0 (2020-06-24)\n\n#### :rocket: Enhancement\n* [#855](https://github.com/ember-cli/eslint-plugin-ember/pull/855) Add new rule [no-assignment-of-untracked-properties-used-in-tracking-contexts](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md) ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#866](https://github.com/ember-cli/eslint-plugin-ember/pull/866) Fix missing import statement in autofix for [no-incorrect-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-incorrect-computed-macros.md) rule ([@bmish](https://github.com/bmish))\n* [#864](https://github.com/ember-cli/eslint-plugin-ember/pull/864) Fix default value of `ignoreClassic` option to be true for [no-computed-properties-in-native-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-computed-properties-in-native-classes.md) rule ([@jaydgruber](https://github.com/jaydgruber))\n* [#857](https://github.com/ember-cli/eslint-plugin-ember/pull/857) Ignore the left side of an assignment (nested path case) in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n* [#856](https://github.com/ember-cli/eslint-plugin-ember/pull/856) Handle nested paths with ES5 setters in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- [@jaydgruber](https://github.com/jaydgruber)\n\n## v8.7.0 (2020-06-15)\n\n#### :rocket: Enhancement\n* [#845](https://github.com/ember-cli/eslint-plugin-ember/pull/845) Add `useOptionalChaining` option (default false) to [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule ([@bmish](https://github.com/bmish)\n* [#840](https://github.com/ember-cli/eslint-plugin-ember/pull/840) Add `includeNativeGetters` option (default false) to [require-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-macros.md) rule ([@bmish](https://github.com/bmish))\n* [#848](https://github.com/ember-cli/eslint-plugin-ember/pull/848) Support optional chaining in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n* [#846](https://github.com/ember-cli/eslint-plugin-ember/pull/846) Support optional chaining in [require-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-macros.md) rule ([@bmish](https://github.com/bmish))\n* [#839](https://github.com/ember-cli/eslint-plugin-ember/pull/839) Support `filterBy` and `mapBy` macros in [require-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-macros.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#842](https://github.com/ember-cli/eslint-plugin-ember/pull/842) Explain why [require-return-from-computed](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-return-from-computed.md) rule does not apply to native classes ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.6.0 (2020-06-02)\n\n#### :rocket: Enhancement\n* [#827](https://github.com/ember-cli/eslint-plugin-ember/pull/827) Add new rule [no-restricted-service-injections](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-restricted-service-injections.md) ([@bmish](https://github.com/bmish))\n* [#826](https://github.com/ember-cli/eslint-plugin-ember/pull/826) Update [no-computed-properties-in-native-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-computed-properties-in-native-classes.md) rule to ignore classes marked `@classic` ([@jaydgruber](https://github.com/jaydgruber))\n\n#### :memo: Documentation\n* [#834](https://github.com/ember-cli/eslint-plugin-ember/pull/834) Add link to jQuery RFCs in [no-jquery](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-jquery.md) rule doc ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#828](https://github.com/ember-cli/eslint-plugin-ember/pull/828) Ensure rule docs mention all rule configuration options ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- [@jaydgruber](https://github.com/jaydgruber)\n\n## v8.5.2 (2020-05-21)\n\n#### :bug: Bug Fix\n* [#821](https://github.com/ember-cli/eslint-plugin-ember/pull/821) Avoid some false positives when detecting if a file is an Ember component, controller, etc ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#823](https://github.com/ember-cli/eslint-plugin-ember/pull/823) Include recommended fix in [no-ember-super-in-es-classes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-ember-super-in-es-classes.md) rule error message ([@GoygovRustam](https://github.com/GoygovRustam))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Rustam Goygov ([@GoygovRustam](https://github.com/GoygovRustam))\n\n## v8.5.1 (2020-05-10)\n\n#### :bug: Bug Fix\n* [#813](https://github.com/ember-cli/eslint-plugin-ember/pull/813) Fix false positive with multiple imports in [prefer-ember-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/prefer-ember-test-helpers.md) rule ([@bmish](https://github.com/bmish))\n* [#812](https://github.com/ember-cli/eslint-plugin-ember/pull/812) Fix false negative when aliasing import in [prefer-ember-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/prefer-ember-test-helpers.md) rule ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#814](https://github.com/ember-cli/eslint-plugin-ember/pull/814) Upgrade to eslint 7 internally ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.5.0 (2020-05-06)\n\n#### :rocket: Enhancement\n* [#795](https://github.com/ember-cli/eslint-plugin-ember/pull/795) Add `catchRouterMicrolib` option (default false) to [no-private-routing-service](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-private-routing-service.md) rule ([@nlfurniss](https://github.com/nlfurniss))\n\n#### :bug: Bug Fix\n* [#802](https://github.com/ember-cli/eslint-plugin-ember/pull/802) Ignore `mirage/config.js` file in [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule ([@BarryThePenguin](https://github.com/BarryThePenguin))\n* [#800](https://github.com/ember-cli/eslint-plugin-ember/pull/800) Handle `@computed` decorator without parentheses in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) and [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rules ([@mongoose700](https://github.com/mongoose700))\n* [#794](https://github.com/ember-cli/eslint-plugin-ember/pull/794) Handle braces without nesting in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#797](https://github.com/ember-cli/eslint-plugin-ember/pull/797) Remove duplicate example from [no-observers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-observers.md) rule doc ([@mehrdadrafiee](https://github.com/mehrdadrafiee))\n\n#### :house: Internal\n* [#801](https://github.com/ember-cli/eslint-plugin-ember/pull/801) Begin testing under Node 14 ([@bmish](https://github.com/bmish))\n\n#### Committers: 5\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jonathan Haines ([@BarryThePenguin](https://github.com/BarryThePenguin))\n- Mehrdad Rafiee ([@mehrdadrafiee](https://github.com/mehrdadrafiee))\n- Michael Peirce ([@mongoose700](https://github.com/mongoose700))\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n\n## v8.4.0 (2020-04-15)\n\n#### :rocket: Enhancement\n* [#767](https://github.com/ember-cli/eslint-plugin-ember/pull/767) Add new rule [prefer-ember-test-helpers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/prefer-ember-test-helpers.md) ([@fierysunset](https://github.com/fierysunset))\n* [#778](https://github.com/ember-cli/eslint-plugin-ember/pull/778) Add new rule [no-test-this-render](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-this-render.md) ([@ventuno](https://github.com/ventuno))\n* [#789](https://github.com/ember-cli/eslint-plugin-ember/pull/789) Add decorator support to [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n* [#790](https://github.com/ember-cli/eslint-plugin-ember/pull/790) Catch assignment in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#786](https://github.com/ember-cli/eslint-plugin-ember/pull/786) Ignore the left side of an assignment in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Connie C Chang ([@fierysunset](https://github.com/fierysunset))\n- [@ventuno](https://github.com/ventuno)\n\n## v8.3.0 (2020-04-14)\n\n#### :rocket: Enhancement\n* [#775](https://github.com/ember-cli/eslint-plugin-ember/pull/775) Add support for explicit getter functions in [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n* [#779](https://github.com/ember-cli/eslint-plugin-ember/pull/779) Add decorator support to [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md) rule ([@bmish](https://github.com/bmish))\n* [#781](https://github.com/ember-cli/eslint-plugin-ember/pull/781) Add decorator support to [no-unnecessary-service-injection-argument](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-unnecessary-service-injection-argument.md) rule ([@bmish](https://github.com/bmish))\n* [#773](https://github.com/ember-cli/eslint-plugin-ember/pull/773) Add autofixer to [no-duplicate-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-duplicate-dependent-keys.md) rule ([@bmish](https://github.com/bmish))\n* [#774](https://github.com/ember-cli/eslint-plugin-ember/pull/774) Catch spaces in [no-invalid-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-dependent-keys.md) rule ([@bmish](https://github.com/bmish))\n* [#768](https://github.com/ember-cli/eslint-plugin-ember/pull/768) Catch leading or trailing periods in [no-invalid-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-dependent-keys.md) ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#771](https://github.com/ember-cli/eslint-plugin-ember/pull/771) Fix false positives in [no-legacy-test-waiters](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-legacy-test-waiters.md) rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.2.0 (2020-04-10)\n\n#### :rocket: Enhancement\n* [#764](https://github.com/ember-cli/eslint-plugin-ember/pull/764) Catch unnecessary braces in [no-invalid-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-dependent-keys.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#759](https://github.com/ember-cli/eslint-plugin-ember/pull/759) Update each rule doc to mention what config enables the rule ([@bmish](https://github.com/bmish))\n* [#758](https://github.com/ember-cli/eslint-plugin-ember/pull/758) Fix typo in example in [no-side-effects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-side-effects.md) rule doc ([@mehrdadrafiee](https://github.com/mehrdadrafiee))\n\n#### :house: Internal\n* [#766](https://github.com/ember-cli/eslint-plugin-ember/pull/766) Add test for [no-replace-test-comments](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-replace-test-comments.md) rule with TODO-prefixed comment ([@bmish](https://github.com/bmish))\n* [#757](https://github.com/ember-cli/eslint-plugin-ember/pull/757) Add tests that configs are exported and mentioned in the README ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Mehrdad Rafiee ([@mehrdadrafiee](https://github.com/mehrdadrafiee))\n\n## v8.1.1 (2020-04-01)\n\n#### :bug: Bug Fix\n* [#752](https://github.com/ember-cli/eslint-plugin-ember/pull/752) Remove [no-empty-attrs](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-empty-attrs.md) from `recommended` config ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#755](https://github.com/ember-cli/eslint-plugin-ember/pull/755) Add note about nullish coalescing operator in [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) rule doc ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.1.0 (2020-03-29)\n\n#### :rocket: Enhancement\n* [#747](https://github.com/ember-cli/eslint-plugin-ember/pull/747) Add autofixer to [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#746](https://github.com/ember-cli/eslint-plugin-ember/pull/746) Do not disable non-recommended rules in the `recommended` config ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#749](https://github.com/ember-cli/eslint-plugin-ember/pull/749) Add missing init hook super calls in rule examples ([@bmish](https://github.com/bmish))\n* [#748](https://github.com/ember-cli/eslint-plugin-ember/pull/748) Switch to new module imports in rule examples ([@bmish](https://github.com/bmish))\n* [#745](https://github.com/ember-cli/eslint-plugin-ember/pull/745) Replace `this.get('property')` with `this.property` in rule examples ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v8.0.0 (2020-03-28)\n\n#### :boom: Breaking Change\n* [#730](https://github.com/ember-cli/eslint-plugin-ember/pull/730) Drop Node 8, 9, and 11 support ([@bmish](https://github.com/bmish))\n* [#729](https://github.com/ember-cli/eslint-plugin-ember/pull/729) Update `ignoreNestedPaths` option default to `false` for [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) rule ([@bmish](https://github.com/bmish))\n* [#731](https://github.com/ember-cli/eslint-plugin-ember/pull/731) Enable additional [recommended](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/recommended-rules.js) rules ([@bmish](https://github.com/bmish))\n  * [no-empty-attrs](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-empty-attrs.md)\n  * [no-get-with-default](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get-with-default.md) (formerly in the [octane](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/octane-rules.js) config)\n  * [no-get](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-get.md) (formerly in the [octane](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/octane-rules.js) config)\n  * [no-incorrect-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-incorrect-computed-macros.md)\n  * [no-invalid-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-dependent-keys.md)\n  * [no-jquery](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-jquery.md) (formerly in the [octane](https://github.com/ember-cli/eslint-plugin-ember/blob/master/lib/octane-rules.js) config)\n  * [no-legacy-test-waiters](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-legacy-test-waiters.md)\n  * [no-mixins](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-mixins.md)\n  * [no-pause-test](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-pause-test.md)\n  * [no-private-routing-service](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-private-routing-service.md)\n  * [no-test-and-then](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-and-then.md)\n  * [no-test-import-export](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-import-export.md)\n  * [no-test-module-for](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-test-module-for.md)\n  * [require-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-macros.md)\n  * [require-computed-property-dependencies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/require-computed-property-dependencies.md)\n  * [use-ember-data-rfc-395-imports](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/use-ember-data-rfc-395-imports.md)\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v7.13.0 (2020-03-28)\n\n#### :rocket: Enhancement\n* [#742](https://github.com/ember-cli/eslint-plugin-ember/pull/742) Detect invalid position of `@each` or `[]` in [no-invalid-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-dependent-keys.md) rule ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#741](https://github.com/ember-cli/eslint-plugin-ember/pull/741) Switch from Travis to GitHub Actions for CI ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v7.12.0 (2020-03-27)\n\n#### :rocket: Enhancement\n* [#738](https://github.com/ember-cli/eslint-plugin-ember/pull/738) Use sets instead of arrays for better performance ([@bmish](https://github.com/bmish))\n* [#702](https://github.com/ember-cli/eslint-plugin-ember/pull/702) Add new rule [no-invalid-test-waiters](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-test-waiters.md) ([@scalvert](https://github.com/scalvert))\n\n#### :memo: Documentation\n* [#737](https://github.com/ember-cli/eslint-plugin-ember/pull/737) Mention tracked properties as a fix for [classic-decorator-no-classic-methods](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/classic-decorator-no-classic-methods.md) ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#732](https://github.com/ember-cli/eslint-plugin-ember/pull/732) Begin testing under Node 13 ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Steve Calvert ([@scalvert](https://github.com/scalvert))\n\n## v7.11.1 (2020-03-25)\n\n#### :bug: Bug Fix\n* [#728](https://github.com/ember-cli/eslint-plugin-ember/pull/728) Allow brace expansion with `and`, `or` macros in [no-incorrect-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-incorrect-computed-macros.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#724](https://github.com/ember-cli/eslint-plugin-ember/pull/724) Recategorize rules in README ([@bmish](https://github.com/bmish))\n* [#723](https://github.com/ember-cli/eslint-plugin-ember/pull/723) Sort rule categories alphabetically in README ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v7.11.0 (2020-03-20)\n\n#### :rocket: Enhancement\n* [#695](https://github.com/ember-cli/eslint-plugin-ember/pull/695) Add new rule [no-incorrect-computed-macros](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-incorrect-computed-macros.md) ([@bmish](https://github.com/bmish))\n* [#709](https://github.com/ember-cli/eslint-plugin-ember/pull/709) Add new rule [no-invalid-dependent-keys](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-dependent-keys.md) ([@TheMBTH](https://github.com/TheMBTH))\n* [#718](https://github.com/ember-cli/eslint-plugin-ember/pull/718) Add new rule [no-replace-test-comments](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-replace-test-comments.md) ([@jaydgruber](https://github.com/jaydgruber))\n* [#705](https://github.com/ember-cli/eslint-plugin-ember/pull/705) Support TypeScript files when checking if rules are running on Ember module or test files ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#719](https://github.com/ember-cli/eslint-plugin-ember/pull/719) Validate imports before reporting violations in [no-invalid-debug-function-arguments](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-invalid-debug-function-arguments.md) rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#716](https://github.com/ember-cli/eslint-plugin-ember/pull/716) Revamp the guide for contributing a new rule ([@bmish](https://github.com/bmish))\n* [#715](https://github.com/ember-cli/eslint-plugin-ember/pull/715) Mention if a rule is auto-fixable in its documentation ([@bmish](https://github.com/bmish))\n* [#713](https://github.com/ember-cli/eslint-plugin-ember/pull/713) Add tests to ensure each rule documentation file has the right title and an examples section ([@bmish](https://github.com/bmish))\n* [#711](https://github.com/ember-cli/eslint-plugin-ember/pull/711) Improve contribution guide for adding new rules ([@TheMBTH](https://github.com/TheMBTH))\n\n#### :house: Internal\n* [#720](https://github.com/ember-cli/eslint-plugin-ember/pull/720) Add tests to ensure some computed property rules handle the @computed decorator ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- L@elaps ([@TheMBTH](https://github.com/TheMBTH))\n- [@jaydgruber](https://github.com/jaydgruber)\n\n## v7.10.1 (2020-03-07)\n\n#### :bug: Bug Fix\n* [#697](https://github.com/ember-cli/eslint-plugin-ember/pull/697) Handle service injections with no arguments in `no-private-routing-service` rule ([@nlfurniss](https://github.com/nlfurniss))\n\n#### Committers: 1\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n\n## v7.10.0 (2020-03-06)\n\n#### :rocket: Enhancement\n* [#694](https://github.com/ember-cli/eslint-plugin-ember/pull/694) Add new rule `no-private-routing-service` ([@nlfurniss](https://github.com/nlfurniss))\n* [#691](https://github.com/ember-cli/eslint-plugin-ember/pull/691) Add new rule `no-mixins` ([@nlfurniss](https://github.com/nlfurniss))\n\n#### Committers: 1\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n\n## v7.9.0 (2020-03-01)\n\n#### :rocket: Enhancement\n* [#595](https://github.com/ember-cli/eslint-plugin-ember/pull/595) Add new rule `no-component-lifecycle-hooks` (included in `octane` config) ([@jbandura](https://github.com/jbandura))\n* [#681](https://github.com/ember-cli/eslint-plugin-ember/pull/681) Add new rule `no-legacy-test-waiters` ([@scalvert](https://github.com/scalvert))\n\n#### :memo: Documentation\n* [#688](https://github.com/ember-cli/eslint-plugin-ember/pull/688) Lint code samples with eslint-plugin-markdown ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jacek Bandura ([@jbandura](https://github.com/jbandura))\n- Steve Calvert ([@scalvert](https://github.com/scalvert))\n\n## v7.8.1 (2020-02-14)\n\n#### :bug: Bug Fix\n* [#674](https://github.com/ember-cli/eslint-plugin-ember/pull/674) Update `require-computed-property-dependencies` rule to handle basic string concatenation in dependent keys ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#669](https://github.com/ember-cli/eslint-plugin-ember/pull/669) Add \"Help Wanted\" section to documentation for rules that are missing native JavaScript class support ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v7.8.0 (2020-02-12)\n\n#### :rocket: Enhancement\n* [#661](https://github.com/ember-cli/eslint-plugin-ember/pull/661) Add new rule `no-controllers` ([@bmish](https://github.com/bmish))\n* [#665](https://github.com/ember-cli/eslint-plugin-ember/pull/665) Update `order-in-*` rules to support custom ordering of properties ([@cdtinney](https://github.com/cdtinney))\n* [#639](https://github.com/ember-cli/eslint-plugin-ember/pull/639) Update `no-observers` rule to catch `addObserver` and observer imports ([@kategengler](https://github.com/kategengler))\n\n#### :bug: Bug Fix\n* [#670](https://github.com/ember-cli/eslint-plugin-ember/pull/670) Update `order-in-*` rules to consider template literals as properties ([@cdtinney](https://github.com/cdtinney))\n* [#664](https://github.com/ember-cli/eslint-plugin-ember/pull/664) Update `no-classic-components` rule to only disallow the specific component import ([@bmish](https://github.com/bmish))\n* [#640](https://github.com/ember-cli/eslint-plugin-ember/pull/640) Update `no-computed-properties-in-native-classes` rule to catch aliasing of computed import ([@kategengler](https://github.com/kategengler))\n\n#### :memo: Documentation\n* [#663](https://github.com/ember-cli/eslint-plugin-ember/pull/663) Update rule docs to use consistent headers and fix markdownlint violations ([@bmish](https://github.com/bmish))\n* [#655](https://github.com/ember-cli/eslint-plugin-ember/pull/655) Update `no-new-mixins` rule documentation ([@efx](https://github.com/efx))\n* [#648](https://github.com/ember-cli/eslint-plugin-ember/pull/648) Fix various spelling mistakes ([@bmish](https://github.com/bmish))\n* [#647](https://github.com/ember-cli/eslint-plugin-ember/pull/647) Fix typo in `no-classic-components` rule documentation ([@rwwagner90](https://github.com/rwwagner90))\n* [#626](https://github.com/ember-cli/eslint-plugin-ember/pull/626) Simplify and clarify rules and configuration sections in README ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#643](https://github.com/ember-cli/eslint-plugin-ember/pull/643) Add missing test case output assertions ([@bmish](https://github.com/bmish))\n\n#### Committers: 5\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Colin Tinney ([@cdtinney](https://github.com/cdtinney))\n- Eli Flanagan ([@efx](https://github.com/efx))\n- Katie Gengler ([@kategengler](https://github.com/kategengler))\n- Robert Wagner ([@rwwagner90](https://github.com/rwwagner90))\n\n## v7.7.2 (2019-12-12)\n\n#### :bug: Bug Fix\n* [#621](https://github.com/ember-cli/eslint-plugin-ember/pull/621) Fix false positive with `ignoreNonThisExpressions` option in `use-ember-get-and-set` rule ([@Exelord](https://github.com/Exelord))\n\n#### :memo: Documentation\n* [#620](https://github.com/ember-cli/eslint-plugin-ember/pull/620) Use consistent prefixes for rule descriptions ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#625](https://github.com/ember-cli/eslint-plugin-ember/pull/625) Add eslint-plugin-jest internally and enable rules ([@bmish](https://github.com/bmish))\n* [#624](https://github.com/ember-cli/eslint-plugin-ember/pull/624) Add eslint-plugin-unicorn internally and enable recommended rules ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Maciej Kwaśniak ([@Exelord](https://github.com/Exelord))\n\n## v7.7.1 (2019-11-29)\n\n#### :bug: Bug Fix\n* [#615](https://github.com/ember-cli/eslint-plugin-ember/pull/615) Fix issue causing assert to fire in `getSourceModuleName` util function ([@patocallaghan](https://github.com/patocallaghan))\n\n#### Committers: 1\n- Pat O'Callaghan ([@patocallaghan](https://github.com/patocallaghan))\n\n## v7.7.0 (2019-11-29)\n\n#### :rocket: Enhancement\n* [#592](https://github.com/ember-cli/eslint-plugin-ember/pull/592) Update `no-classic-classes` rule to catch classic Ember Data model classes ([@patocallaghan](https://github.com/patocallaghan))\n\n#### :bug: Bug Fix\n* [#610](https://github.com/ember-cli/eslint-plugin-ember/pull/610) Fix invalid `no-get` rule autofix caused by invalid JS variable name ([@bmish](https://github.com/bmish))\n* [#607](https://github.com/ember-cli/eslint-plugin-ember/pull/607) Fix spread property bug in `require-super-in-init` rule ([@bmish](https://github.com/bmish))\n* [#600](https://github.com/ember-cli/eslint-plugin-ember/pull/600) Add missing schema validation for options on many rules ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#611](https://github.com/ember-cli/eslint-plugin-ember/pull/611) Add many missing tests for lines without test coverage ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Pat O'Callaghan ([@patocallaghan](https://github.com/patocallaghan))\n\n## v7.6.0 (2019-11-19)\n\n#### :rocket: Enhancement\n* [#594](https://github.com/ember-cli/eslint-plugin-ember/pull/594) Add new rule `no-get-with-default` ([@steventsao](https://github.com/steventsao))\n\n#### Committers: 1\n- Steven Tsao ([@steventsao](https://github.com/steventsao))\n\n## v7.5.0 (2019-11-11)\n\n#### :rocket: Enhancement\n* [#583](https://github.com/ember-cli/eslint-plugin-ember/pull/583) Update `no-observers` rule to handle decorators ([@bmish](https://github.com/bmish))\n* [#577](https://github.com/ember-cli/eslint-plugin-ember/pull/577) Add autofixer to `no-get` rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#586](https://github.com/ember-cli/eslint-plugin-ember/pull/586) Update `use-brace-expansion` rule to only report the string arguments of a computed property as the violation (and not the entire function body) ([@bmish](https://github.com/bmish))\n* [#581](https://github.com/ember-cli/eslint-plugin-ember/pull/581) Update `no-get` rule to ignore `get()` usages inside objects implementing `unknownProperty()` ([@bmish](https://github.com/bmish))\n* [#580](https://github.com/ember-cli/eslint-plugin-ember/pull/580) Update `no-get` rule to ignore `get()` usages inside proxy objects ([@bmish](https://github.com/bmish))\n* [#579](https://github.com/ember-cli/eslint-plugin-ember/pull/579) Fix handling of multi-line property keys by `require-computed-property-dependencies` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#588](https://github.com/ember-cli/eslint-plugin-ember/pull/588) Add `type` meta property to each rule ([@bmish](https://github.com/bmish))\n* [#587](https://github.com/ember-cli/eslint-plugin-ember/pull/587) And missing rule documentation URLs ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#582](https://github.com/ember-cli/eslint-plugin-ember/pull/582) Add CI check to ensure `yarn update` is run to update docs/autogenerated files ([@bmish](https://github.com/bmish))\n* [#574](https://github.com/ember-cli/eslint-plugin-ember/pull/574) Update `reportUnorderedProperties` util function to also work with native classes  ([@laurmurclar](https://github.com/laurmurclar))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Laura Murphy-Clarkin ([@laurmurclar](https://github.com/laurmurclar))\n\n## v7.4.1 (2019-11-07)\n\n#### :bug: Bug Fix\n* [#575](https://github.com/ember-cli/eslint-plugin-ember/pull/575) Update `avoid-leaking-state-in-ember-objects` rule to handle logical expressions ([@alexlafroscia](https://github.com/alexlafroscia))\n* [#571](https://github.com/ember-cli/eslint-plugin-ember/pull/571) Update `avoid-leaking-state-in-ember-objects` rule to handle ternary expressions ([@alexlafroscia](https://github.com/alexlafroscia))\n* [#573](https://github.com/ember-cli/eslint-plugin-ember/pull/573) Update `require-computed-macros` rule to handle `this.get('property')` (in addition to `this.property`) ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Alex LaFroscia ([@alexlafroscia](https://github.com/alexlafroscia))\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v7.4.0 (2019-11-06)\n\n#### :rocket: Enhancement\n* [#561](https://github.com/ember-cli/eslint-plugin-ember/pull/561) Add `octane` configuration (experimental) ([@patocallaghan](https://github.com/patocallaghan))\n* [#562](https://github.com/ember-cli/eslint-plugin-ember/pull/562) Add new rule `no-test-module-for` ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#565](https://github.com/ember-cli/eslint-plugin-ember/pull/565) Add `ignoreNestedPaths` option (default true) to `no-get` rule ([@bmish](https://github.com/bmish))\n* [#564](https://github.com/ember-cli/eslint-plugin-ember/pull/564) Update `no-new-mixins` rule to handle native classes ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#570](https://github.com/ember-cli/eslint-plugin-ember/pull/570) Simplify some tests by setting `parserOptions` globally instead of in each individual test case ([@bmish](https://github.com/bmish))\n* [#568](https://github.com/ember-cli/eslint-plugin-ember/pull/568) Add tests to ensure plugin exports correct configurations ([@bmish](https://github.com/bmish))\n* [#563](https://github.com/ember-cli/eslint-plugin-ember/pull/563) Lint against unnecessary template literals internally ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- L. Preston Sego III ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n- Pat O'Callaghan ([@patocallaghan](https://github.com/patocallaghan))\n\n## v7.3.0 (2019-10-30)\n\n#### :rocket: Enhancement\n* [#555](https://github.com/ember-cli/eslint-plugin-ember/pull/555) Add new `no-actions-hash` rule ([@laurmurclar](https://github.com/laurmurclar))\n* [#548](https://github.com/ember-cli/eslint-plugin-ember/pull/548) Add new `require-tagless-components` rule ([@alexlafroscia](https://github.com/alexlafroscia))\n* [#552](https://github.com/ember-cli/eslint-plugin-ember/pull/552) Add new `no-classic-classes` rule ([@alexlafroscia](https://github.com/alexlafroscia))\n* [#551](https://github.com/ember-cli/eslint-plugin-ember/pull/551) Add new `no-classic-components` rule ([@mikoscz](https://github.com/mikoscz))\n* [#546](https://github.com/ember-cli/eslint-plugin-ember/pull/546) Add new `no-computed-properties-in-native-classes` rule ([@patocallaghan](https://github.com/patocallaghan))\n\n#### :bug: Bug Fix\n* [#553](https://github.com/ember-cli/eslint-plugin-ember/pull/553) Avoid crash from missing function check in `require-super-in-init` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#554](https://github.com/ember-cli/eslint-plugin-ember/pull/554) Add rule documentation template ([@bmish](https://github.com/bmish))\n* [#550](https://github.com/ember-cli/eslint-plugin-ember/pull/550) Modernize documentation for `alias-model-in-controller` rule ([@alexlafroscia](https://github.com/alexlafroscia))\n\n#### :house: Internal\n* [#558](https://github.com/ember-cli/eslint-plugin-ember/pull/558) Update `isEmberCoreModule` util function to also handle native classes (in addition to classic classes) ([@laurmurclar](https://github.com/laurmurclar))\n\n#### Committers: 5\n- Alex LaFroscia ([@alexlafroscia](https://github.com/alexlafroscia))\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Laura Murphy-Clarkin ([@laurmurclar](https://github.com/laurmurclar))\n- Michał Staśkiewicz ([@mikoscz](https://github.com/mikoscz))\n- Pat O'Callaghan ([@patocallaghan](https://github.com/patocallaghan))\n\n## v7.2.0 (2019-10-20)\n\n#### :rocket: Enhancement\n* [#545](https://github.com/ember-cli/eslint-plugin-ember/pull/545) Add `ignoreNonThisExpressions` option to `use-ember-get-and-set` rule ([@nlfurniss](https://github.com/nlfurniss))\n* [#534](https://github.com/ember-cli/eslint-plugin-ember/pull/534) Add `onlyThisContexts` option to `no-arrow-function-computed-properties` rule ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n* [#537](https://github.com/ember-cli/eslint-plugin-ember/pull/537) Add `ignoreGetProperties` option for `no-get` rule ([@EvgenyOrekhov](https://github.com/EvgenyOrekhov))\n\n#### :house: Internal\n* [#462](https://github.com/ember-cli/eslint-plugin-ember/pull/462) Refactor null checks for `new-module-imports` and `use-ember-data-rfc-395-imports` rules ([@dcyriller](https://github.com/dcyriller))\n* [#528](https://github.com/ember-cli/eslint-plugin-ember/pull/528) Add eslint-plugin-node and enable recommended rules internally ([@bmish](https://github.com/bmish))\n* [#524](https://github.com/ember-cli/eslint-plugin-ember/pull/524) Add eslint-plugin-filenames to enforce kebab-case filenames ([@bmish](https://github.com/bmish))\n* [#523](https://github.com/ember-cli/eslint-plugin-ember/pull/523) Add eslint-plugin-eslint-comments and fix violations ([@bmish](https://github.com/bmish))\n\n#### Committers: 5\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Cyrille David ([@dcyriller](https://github.com/dcyriller))\n- Evgeny Orekhov ([@EvgenyOrekhov](https://github.com/EvgenyOrekhov))\n- L. Preston Sego III ([@NullVoxPopuli](https://github.com/NullVoxPopuli))\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n\n## v7.1.0 (2019-09-18)\n\n#### :rocket: Enhancement\n* [#507](https://github.com/ember-cli/eslint-plugin-ember/pull/507) Add new `no-pause-test` rule ([@scottkidder](https://github.com/scottkidder))\n\n#### :bug: Bug Fix\n* [#511](https://github.com/ember-cli/eslint-plugin-ember/pull/511) Avoid crash from empty return statement in `require-computed-macros` rule ([@bmish](https://github.com/bmish))\n* [#512](https://github.com/ember-cli/eslint-plugin-ember/pull/512) Avoid crash when missing arguments to `this.route()` in `route-path-style` rule ([@bmish](https://github.com/bmish))\n* [#498](https://github.com/ember-cli/eslint-plugin-ember/pull/498) Fix decorator handling and improve error messages for `computed-property-getters` rule ([@Exelord](https://github.com/Exelord))\n* [#504](https://github.com/ember-cli/eslint-plugin-ember/pull/504) Update `require-computed-property-dependencies` rule to ignore injected service names by default ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#514](https://github.com/ember-cli/eslint-plugin-ember/pull/514) Hide the deprecated rules section from the README if empty ([@bmish](https://github.com/bmish))\n* [#510](https://github.com/ember-cli/eslint-plugin-ember/pull/510) Cleanup code samples in some of the new V7 recommended rules ([@bmish](https://github.com/bmish))\n* [#496](https://github.com/ember-cli/eslint-plugin-ember/pull/496) Suggest using the eslint `consistent-return` rule alongside `require-return-from-computed` rule to help avoid false positives ([@bmish](https://github.com/bmish))\n* [#506](https://github.com/ember-cli/eslint-plugin-ember/pull/506) Add example of a getter with an if statement for `require-return-from-computed` rule ([@bradleypriest](https://github.com/bradleypriest))\n\n#### :house: Internal\n* [#519](https://github.com/ember-cli/eslint-plugin-ember/pull/519) Add CI check to ensure yarn.lock is up-to-date ([@bmish](https://github.com/bmish))\n* [#516](https://github.com/ember-cli/eslint-plugin-ember/pull/516) Test plugin under both eslint 5 and eslint 6 ([@bmish](https://github.com/bmish))\n* [#515](https://github.com/ember-cli/eslint-plugin-ember/pull/515) Add eslint-plugin-eslint-plugin and enable/autofix most rules ([@bmish](https://github.com/bmish))\n* [#505](https://github.com/ember-cli/eslint-plugin-ember/pull/505) Enforce minimum test coverage ([@bmish](https://github.com/bmish))\n* [#503](https://github.com/ember-cli/eslint-plugin-ember/pull/503) Refactor utils to move type checking utils to a separate file and alphabetize ([@bmish](https://github.com/bmish))\n\n#### Committers: 4\n- Bradley Priest ([@bradleypriest](https://github.com/bradleypriest))\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Maciej Kwaśniak ([@Exelord](https://github.com/Exelord))\n- Scott Kidder ([@scottkidder](https://github.com/scottkidder))\n\n## v7.0.0 (2019-08-23)\n\n#### :boom: Breaking Change\n* [#491](https://github.com/ember-cli/eslint-plugin-ember/pull/491) Drop eslint 4 support ([@bmish](https://github.com/bmish))\n* [#486](https://github.com/ember-cli/eslint-plugin-ember/pull/486) Enable additional recommended rules (`no-arrow-function-computed-properties`, `no-deeply-nested-dependent-keys-with-each`, `no-ember-super-in-es-classes`, `no-incorrect-calls-with-inline-anonymous-functions`, `no-invalid-debug-function-arguments`, `no-new-mixins`, `no-unnecessary-route-path-option`, `no-volatile-computed-properties`, `require-return-from-computed`) ([@bmish](https://github.com/bmish))\n* [#487](https://github.com/ember-cli/eslint-plugin-ember/pull/487) Remove deprecated rules (`avoid-leaking-state-in-components`, `local-modules`, `no-get-properties`) ([@bmish](https://github.com/bmish))\n* [#485](https://github.com/ember-cli/eslint-plugin-ember/pull/485) Drop Node 6 support ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.10.1 (2019-08-23)\n\n#### :bug: Bug Fix\n* [#488](https://github.com/ember-cli/eslint-plugin-ember/pull/488) Update `require-computed-property-dependencies` rule to support eslint 3 and 4 ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#489](https://github.com/ember-cli/eslint-plugin-ember/pull/489) Document eslint 4 as the minimum supported version ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#482](https://github.com/ember-cli/eslint-plugin-ember/pull/482) Start testing plugin under Node 12 ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.10.0 (2019-08-19)\n\n#### :rocket: Enhancement\n* [#473](https://github.com/ember-cli/eslint-plugin-ember/pull/473) Add new `no-incorrect-calls-with-inline-anonymous-functions` rule ([@raycohen](https://github.com/raycohen))\n\n#### :bug: Bug Fix\n* [#476](https://github.com/ember-cli/eslint-plugin-ember/pull/476) Add `allowDynamicKeys` option (default true) to `require-computed-property-dependencies` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Ray Cohen ([@raycohen](https://github.com/raycohen))\n\n## v6.9.1 (2019-08-14)\n\n#### :bug: Bug Fix\n* [#472](https://github.com/ember-cli/eslint-plugin-ember/pull/472) Improve handling of nested keys inside braces for `require-computed-property-dependencies` rule ([@bmish](https://github.com/bmish))\n* [#471](https://github.com/ember-cli/eslint-plugin-ember/pull/471) Improve detection of missing dependencies in `require-computed-property-dependencies` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.9.0 (2019-08-12)\n\n#### :rocket: Enhancement\n* [#458](https://github.com/ember-cli/eslint-plugin-ember/pull/458) Add new rule `require-computed-property-dependencies` ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#463](https://github.com/ember-cli/eslint-plugin-ember/pull/463) Fix false positives for import statements with `use-ember-data-rfc-395-imports` rule ([@fusion2004](https://github.com/fusion2004))\n\n#### :house: Internal\n* [#465](https://github.com/ember-cli/eslint-plugin-ember/pull/465) Add tests that rules are setup correctly (not missing tests, docs, exports, etc) ([@bmish](https://github.com/bmish))\n* [#466](https://github.com/ember-cli/eslint-plugin-ember/pull/466) Fix eslint 6 rule test parser error ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Mark Oleson ([@fusion2004](https://github.com/fusion2004))\n\n## v6.8.2 (2019-08-08)\n\n#### :bug: Bug Fix\n* [#461](https://github.com/ember-cli/eslint-plugin-ember/pull/461) Add null check in `new-module-imports` rule (again) ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.8.1 (2019-08-08)\n\n#### :bug: Bug Fix\n* [#460](https://github.com/ember-cli/eslint-plugin-ember/pull/460) Add null check in `new-module-imports` rule ([@dcyriller](https://github.com/dcyriller))\n\n#### Committers: 1\n- Cyrille David ([@dcyriller](https://github.com/dcyriller))\n\n## v6.8.0 (2019-08-08)\n\n#### :rocket: Enhancement\n* [#450](https://github.com/ember-cli/eslint-plugin-ember/pull/450) Add new `use-ember-data-rfc-395-imports` rule ([@dcyriller](https://github.com/dcyriller))\n* [#457](https://github.com/ember-cli/eslint-plugin-ember/pull/457) Add new `no-arrow-function-computed-properties` rule ([@bmish](https://github.com/bmish))\n* [#445](https://github.com/ember-cli/eslint-plugin-ember/pull/445) Add new `no-proxies` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#454](https://github.com/ember-cli/eslint-plugin-ember/pull/454) Improve justification for `no-observers` rule ([@efx](https://github.com/efx))\n\n#### :house: Internal\n* [#456](https://github.com/ember-cli/eslint-plugin-ember/pull/456) Upgrade from eslint 4 to 5 ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Cyrille David ([@dcyriller](https://github.com/dcyriller))\n- Eli Flanagan ([@efx](https://github.com/efx))\n\n## v6.7.1 (2019-07-02)\n\n#### :bug: Bug Fix\n* [#440](https://github.com/ember-cli/eslint-plugin-ember/pull/440) Add missing rules `classic-decorator-hooks` and `classic-decorator-no-classic-methods` to index.js ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.7.0 (2019-06-22)\n\n#### :rocket: Enhancement\n* [#436](https://github.com/ember-cli/eslint-plugin-ember/pull/436) Adds decorator rules to aid migration to Octane ([@pzuraq](https://github.com/pzuraq))\n* [#434](https://github.com/ember-cli/eslint-plugin-ember/pull/434) Add new `no-volatile-computed-properties` rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#432](https://github.com/ember-cli/eslint-plugin-ember/pull/432) Update `require-computed-macros` rule to suggest the `reads` macro instead of `readOnly` for computed properties with `return this.x` ([@bmish](https://github.com/bmish))\n\n#### :house: Internal\n* [#435](https://github.com/ember-cli/eslint-plugin-ember/pull/435) Update ESLint config incl. Prettier ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 4\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Chris Garrett ([@pzuraq](https://github.com/pzuraq))\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n- [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)\n\n\n## v6.6.0 (2019-06-16)\n\n#### :rocket: Enhancement\n* [#429](https://github.com/ember-cli/eslint-plugin-ember/pull/429) Add new `require-computed-macros` rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#428](https://github.com/ember-cli/eslint-plugin-ember/pull/428) Fix spread operator bug in `no-on-calls-in-components` rule ([@rajasegar](https://github.com/rajasegar))\n\n#### :memo: Documentation\n* [#431](https://github.com/ember-cli/eslint-plugin-ember/pull/431) Add link to `sendAction` deprecation RFC for `closure-actions` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Rajasegar Chandran ([@rajasegar](https://github.com/rajasegar))\n\n## v6.5.1 (2019-05-27)\n\n#### :bug: Bug Fix\n* [#427](https://github.com/ember-cli/eslint-plugin-ember/pull/427) Fix typo in error message for `no-get` rule ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.5.0 (2019-05-26)\n\n#### :rocket: Enhancement\n* [#421](https://github.com/ember-cli/eslint-plugin-ember/pull/421) Update the `no-get` rule to also handle the `getProperties` function, and mark the `no-get-properties` rule as deprecated ([@bmish](https://github.com/bmish))\n* [#397](https://github.com/ember-cli/eslint-plugin-ember/pull/397) Add new `computed-property-getters` rule ([@jrjohnson](https://github.com/jrjohnson))\n\n#### :memo: Documentation\n* [#412](https://github.com/ember-cli/eslint-plugin-ember/pull/412) Update release instructions ([@bmish](https://github.com/bmish))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Jonathan Johnson ([@jrjohnson](https://github.com/jrjohnson))\n\n## v6.4.1 (2019-04-21)\n\n#### :bug: Bug Fix\n* [#413](https://github.com/ember-cli/eslint-plugin-ember/pull/413) Ignore template literals in `no-get` and `no-get-properties` rules ([@bmish](https://github.com/bmish))\n\n#### Committers: 1\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n\n## v6.4.0 (2019-04-21)\n\n#### :rocket: Enhancement\n* [#403](https://github.com/ember-cli/eslint-plugin-ember/pull/403) Add new `no-get-properties` rule ([@bmish](https://github.com/bmish))\n* [#404](https://github.com/ember-cli/eslint-plugin-ember/pull/404) Add new `no-get` rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#398](https://github.com/ember-cli/eslint-plugin-ember/pull/398) `no-unnecessary-route-path-option`: fix error when `path` is undefined ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#411](https://github.com/ember-cli/eslint-plugin-ember/pull/411) Update contributors ([@bmish](https://github.com/bmish))\n* [#409](https://github.com/ember-cli/eslint-plugin-ember/pull/409) Update documentation for `require-return-from-computed` rule ([@esbanarango](https://github.com/esbanarango))\n\n#### Committers: 2\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Esteban Arango Medina ([@esbanarango](https://github.com/esbanarango))\n\n## v6.3.0 (2019-03-19)\n\n#### :rocket: Enhancement\n* [#369](https://github.com/ember-cli/eslint-plugin-ember/pull/369) Add new 'route-path-style' rule ([@bmish](https://github.com/bmish))\n* [#372](https://github.com/ember-cli/eslint-plugin-ember/pull/372) Add new 'no-unnecessary-index-route'  rule ([@bmish](https://github.com/bmish))\n* [#262](https://github.com/ember-cli/eslint-plugin-ember/pull/262) Add new 'require-return-from-computed' rule ([@gmurphey](https://github.com/gmurphey))\n* [#378](https://github.com/ember-cli/eslint-plugin-ember/pull/378) Add new `no-unnecessary-service-injection-argument` rule ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#395](https://github.com/ember-cli/eslint-plugin-ember/pull/395) docs: improve `closure-action` rule examples ([@Caltor](https://github.com/Caltor))\n* [#383](https://github.com/ember-cli/eslint-plugin-ember/pull/383) no-deeply-nested-dependent-keys-with-each: Fix documentation examples ([@Alonski](https://github.com/Alonski))\n\n#### :house: Internal\n* [#386](https://github.com/ember-cli/eslint-plugin-ember/pull/386) test: add null output assertions for lint rules / test cases with no autofixer. ([@bmish](https://github.com/bmish))\n\n#### Committers: 4\n- Alon Bukai ([@Alonski](https://github.com/Alonski))\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Garrett Murphey ([@gmurphey](https://github.com/gmurphey))\n- [@Caltor](https://github.com/Caltor)\n\n## v6.2.0 (2019-01-28)\n\n#### :rocket: Enhancement\n* [#375](https://github.com/ember-cli/eslint-plugin-ember/pull/375) no-test-and-then: slight optimization ([@bmish](https://github.com/bmish))\n* [#373](https://github.com/ember-cli/eslint-plugin-ember/pull/373) no-unnecessary-route-path-option: Add support for `--fix` ([@bmish](https://github.com/bmish))\n* [#370](https://github.com/ember-cli/eslint-plugin-ember/pull/370) Add `no-unnecessary-route-path-option` rule ([@bmish](https://github.com/bmish))\n* [#365](https://github.com/ember-cli/eslint-plugin-ember/pull/365) no-invalid-debug-function-arguments: Use dynamic error message ([@bmish](https://github.com/bmish))\n* [#364](https://github.com/ember-cli/eslint-plugin-ember/pull/364) assert-arg-order: Rename to `no-invalid-debug-function-arguments` and detect invalid usages of `deprecate` and `warn` too ([@bmish](https://github.com/bmish))\n* [#358](https://github.com/ember-cli/eslint-plugin-ember/pull/358) Add `assert-arg-order` rule ([@bmish](https://github.com/bmish))\n* [#359](https://github.com/ember-cli/eslint-plugin-ember/pull/359) Add `no-deeply-nested-dependent-keys-with-each` rule ([@bmish](https://github.com/bmish))\n* [#357](https://github.com/ember-cli/eslint-plugin-ember/pull/357) Add `no-test-and-then` rule ([@bmish](https://github.com/bmish))\n\n#### :bug: Bug Fix\n* [#371](https://github.com/ember-cli/eslint-plugin-ember/pull/371) no-test-and-then: Run only on test files ([@bmish](https://github.com/bmish))\n* [#367](https://github.com/ember-cli/eslint-plugin-ember/pull/367) no-deeply-nested-dependent-keys-with-each: Fix false positives ([@bmish](https://github.com/bmish))\n* [#366](https://github.com/ember-cli/eslint-plugin-ember/pull/366) no-invalid-debug-function-arguments: Fix false positives ([@bmish](https://github.com/bmish))\n* [#362](https://github.com/ember-cli/eslint-plugin-ember/pull/362) assert-arg-order: Fix rule for `Ember.assert()` case ([@bmish](https://github.com/bmish))\n\n#### :memo: Documentation\n* [#380](https://github.com/ember-cli/eslint-plugin-ember/pull/380) no-test-and-then: Add migration path docs ([@bmish](https://github.com/bmish))\n* [#368](https://github.com/ember-cli/eslint-plugin-ember/pull/368) no-test-and-then: Fix code sample in docs ([@bmish](https://github.com/bmish))\n* [#361](https://github.com/ember-cli/eslint-plugin-ember/pull/361) no-deeply-nested-dependent-keys-with-each: Fix docs typo ([@bmish](https://github.com/bmish))\n* [#360](https://github.com/ember-cli/eslint-plugin-ember/pull/360) Use more specific array types in util jsdoc comments ([@bmish](https://github.com/bmish))\n* [#355](https://github.com/ember-cli/eslint-plugin-ember/pull/355) avoid-leaking-state-in-ember-objects: Show usage example of `DEFAULT_IGNORED_PROPERTIES` ([@yoavfranco](https://github.com/yoavfranco))\n* [#354](https://github.com/ember-cli/eslint-plugin-ember/pull/354) avoid-needs-in-controllers: Add documentation ([@quajo](https://github.com/quajo))\n\n#### :house: Internal\n* [#363](https://github.com/ember-cli/eslint-plugin-ember/pull/363) no-deeply-nested-dependent-keys-with-each: Add more tests ([@bmish](https://github.com/bmish))\n\n#### Committers: 3\n- Bryan Mishkin ([@bmish](https://github.com/bmish))\n- Selase Krakani ([@quajo](https://github.com/quajo))\n- Yoav M. Franco ([@yoavfranco](https://github.com/yoavfranco))\n\n\n## v6.1.0 (2018-12-15)\n\n#### :rocket: Enhancement\n* [#350](https://github.com/ember-cli/eslint-plugin-ember/pull/350) Introduce `no-ember-super-in-es-classes` rule ([@dfreeman](https://github.com/dfreeman))\n* [#298](https://github.com/ember-cli/eslint-plugin-ember/pull/298) no-jquery: Check for aliased imports from 'jquery' module ([@initram](https://github.com/initram))\n\n#### :bug: Bug Fix\n* [#353](https://github.com/ember-cli/eslint-plugin-ember/pull/353) Fix error with `avoid-leaking-state-in-ember-objects` and spread ([@nlfurniss](https://github.com/nlfurniss))\n* [#348](https://github.com/ember-cli/eslint-plugin-ember/pull/348) Fix `no-restricted-resolver-tests` to narrow scope of rule ([@scalvert](https://github.com/scalvert))\n* [#332](https://github.com/ember-cli/eslint-plugin-ember/pull/332) use-brace-expansion: Limit lint rule to only trigger for `computed()` but no other macros ([@gmurphey](https://github.com/gmurphey))\n\n#### :memo: Documentation\n* [#349](https://github.com/ember-cli/eslint-plugin-ember/pull/349) Update `avoid-leaking-state-in-ember-objects` documentation. ([@samselikoff](https://github.com/samselikoff))\n* [#345](https://github.com/ember-cli/eslint-plugin-ember/pull/345) Fix typo on `getNoPOJOWithoutIntegrationTrueMessage`. ([@esbanarango](https://github.com/esbanarango))\n* [#341](https://github.com/ember-cli/eslint-plugin-ember/pull/341) Clarify `no-ember-testing-in-module-scope` documentation. ([@cibernox](https://github.com/cibernox))\n\n#### :house: Internal\n* [#347](https://github.com/ember-cli/eslint-plugin-ember/pull/347) TravisCI: Remove deprecated `sudo: false` option ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 9\n- Dan Freeman ([@dfreeman](https://github.com/dfreeman))\n- Esteban Arango Medina ([@esbanarango](https://github.com/esbanarango))\n- Garrett Murphey ([@gmurphey](https://github.com/gmurphey))\n- Martin Midtgaard ([@initram](https://github.com/initram))\n- Miguel Camba ([@cibernox](https://github.com/cibernox))\n- Nathaniel Furniss ([@nlfurniss](https://github.com/nlfurniss))\n- Sam Selikoff ([@samselikoff](https://github.com/samselikoff))\n- Steve Calvert ([@scalvert](https://github.com/scalvert))\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n\n## v6.0.1 (2018-11-16)\n\n#### :rocket: Enhancement\n* [#331](https://github.com/ember-cli/eslint-plugin-ember/pull/331) Updating no-side-effects to also report on setProperties. ([@gmurphey](https://github.com/gmurphey))\n\n#### :bug: Bug Fix\n* [#340](https://github.com/ember-cli/eslint-plugin-ember/pull/340) no-restricted-resolver: Fix crashes ([@Turbo87](https://github.com/Turbo87))\n\n#### :house: Internal\n* [#335](https://github.com/ember-cli/eslint-plugin-ember/pull/335) Remove outdated `.nvmrc` file ([@Turbo87](https://github.com/Turbo87))\n* [#334](https://github.com/ember-cli/eslint-plugin-ember/pull/334) package.json: Limit published files to the `lib` folder ([@Turbo87](https://github.com/Turbo87))\n* [#336](https://github.com/ember-cli/eslint-plugin-ember/pull/336) CI: Use `--runInBand` option of Jest to speed up tests ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 2\n- Garrett Murphey ([@gmurphey](https://github.com/gmurphey))\n- Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))\n\n\n## v6.0.0 (2018-11-14)\n\nThis release includes several changes to the `ember/recommended` configuration,\nand drops support for Node.js 4 and ESLint 3.\n\n#### :boom: Breaking Change\n* [#311](https://github.com/ember-cli/eslint-plugin-ember/pull/311) Add `avoid-using-needs-in-controllers` to recommended set. ([@rwjblue](https://github.com/rwjblue))\n* [#310](https://github.com/ember-cli/eslint-plugin-ember/pull/310) Add `no-restricted-resolver-tests` to recommended. ([@rwjblue](https://github.com/rwjblue))\n* [#309](https://github.com/ember-cli/eslint-plugin-ember/pull/309) Make `no-observers` rule recommended ([@Gaurav0](https://github.com/Gaurav0))\n* [#274](https://github.com/ember-cli/eslint-plugin-ember/pull/274) Add `no-ember-testing-in-module-scope` to recommended ([@tmquinn](https://github.com/tmquinn))\n* [#267](https://github.com/ember-cli/eslint-plugin-ember/pull/267) Remove deprecated `experimentalObjectRestSpread` option ([@scottkidder](https://github.com/scottkidder))\n* [#255](https://github.com/ember-cli/eslint-plugin-ember/pull/255) Drop Node 4 support. ([@rwjblue](https://github.com/rwjblue))\n\n#### :rocket: Enhancement\n* [#311](https://github.com/ember-cli/eslint-plugin-ember/pull/311) Add `avoid-using-needs-in-controllers` to recommended set. ([@rwjblue](https://github.com/rwjblue))\n* [#310](https://github.com/ember-cli/eslint-plugin-ember/pull/310) Add no-restricted-resolver-tests to recommended. ([@rwjblue](https://github.com/rwjblue))\n* [#309](https://github.com/ember-cli/eslint-plugin-ember/pull/309) Make no-observers rule recommended ([@Gaurav0](https://github.com/Gaurav0))\n\n#### Committers: 4\n- Gaurav Munjal ([@Gaurav0](https://github.com/Gaurav0))\n- Quinn Hoyer ([@tmquinn](https://github.com/tmquinn))\n- Robert Jackson ([@rwjblue](https://github.com/rwjblue))\n- Scott Kidder ([@scottkidder](https://github.com/scottkidder))\n\n\n## v5.4.0 (2018-11-09)\n\n#### :rocket: Enhancement\n* [#253](https://github.com/ember-cli/eslint-plugin-ember/pull/253) Add `avoid-using-needs` rule. ([@twokul](https://github.com/twokul))\n\n#### :bug: Bug Fix\n* [#314](https://github.com/ember-cli/eslint-plugin-ember/pull/314) Adding missing rules to index.js. ([@gmurphey](https://github.com/gmurphey))\n\n#### Committers: 2\n- Alex Navasardyan ([twokul](https://github.com/twokul))\n- Garrett Murphey ([gmurphey](https://github.com/gmurphey))\n\n\n## v5.3.0 (2018-11-08)\n\n#### :rocket: Enhancement\n* [#278](https://github.com/ember-cli/eslint-plugin-ember/pull/278) Adding `no-restricted-resolver-tests` general rule. ([@scalvert](https://github.com/scalvert))\n* [#272](https://github.com/ember-cli/eslint-plugin-ember/pull/272) Add new rule `no-ember-testing-in-module-scope`. ([@tmquinn](https://github.com/tmquinn))\n* [#261](https://github.com/ember-cli/eslint-plugin-ember/pull/261) Adding `no-test-file-importing` rule. ([@step2yeung](https://github.com/step2yeung))\n* [#256](https://github.com/ember-cli/eslint-plugin-ember/pull/256) Add `no-new-mixins` rule. ([@nlfurniss](https://github.com/nlfurniss))\n\n#### :bug: Bug Fix\n* [#299](https://github.com/ember-cli/eslint-plugin-ember/pull/299) Fix issue with `no-duplicate-dependent-keys` to avoid errors on non-string dependent keys. ([@initram](https://github.com/initram))\n* [#260](https://github.com/ember-cli/eslint-plugin-ember/pull/260) Updating `no-side-effects` rule to better detect sets inside of blocks. ([@gmurphey](https://github.com/gmurphey))\n* [#246](https://github.com/ember-cli/eslint-plugin-ember/pull/246) Updating `no-on-calls-in-components` to only fail components using on with lifecylcle hooks. ([@patience-tema-baron](https://github.com/patience-tema-baron))\n\n#### :memo: Documentation\n* [#276](https://github.com/ember-cli/eslint-plugin-ember/pull/276) Add reason for rule `no-on-calls-in-components`. ([@cbou](https://github.com/cbou))\n* [#277](https://github.com/ember-cli/eslint-plugin-ember/pull/277) docs: remove `get` from closure action example. ([@knownasilya](https://github.com/knownasilya))\n* [#266](https://github.com/ember-cli/eslint-plugin-ember/pull/266) Update `no-empty-attrs` description. ([@locks](https://github.com/locks))\n\n#### :house: Internal\n* [#280](https://github.com/ember-cli/eslint-plugin-ember/pull/280) Clean up a couple of test definitions to unblock update script. ([@tmquinn](https://github.com/tmquinn))\n\n#### Committers: 12\n- Garrett Murphey ([gmurphey](https://github.com/gmurphey))\n- Ilya Radchenko ([knownasilya](https://github.com/knownasilya))\n- Martin Midtgaard ([initram](https://github.com/initram))\n- Nathaniel Furniss ([nlfurniss](https://github.com/nlfurniss))\n- Patience Tema Baron ([patience-tema-baron](https://github.com/patience-tema-baron))\n- Quinn Hoyer ([tmquinn](https://github.com/tmquinn))\n- Ricardo Mendes ([locks](https://github.com/locks))\n- Stephen Yeung ([step2yeung](https://github.com/step2yeung))\n- Steve Calvert ([scalvert](https://github.com/scalvert))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n- [dependabot[bot]](https://github.com/apps/dependabot)\n- charles bourasseau ([cbou](https://github.com/cbou))\n\n\n## v5.2.0 (2018-05-15)\n\n#### :rocket: Enhancement\n* [#142](https://github.com/ember-cli/eslint-plugin-ember/pull/142) Port code to ember-rfc176-data new format. ([@Serabe](https://github.com/Serabe))\n* [#245](https://github.com/ember-cli/eslint-plugin-ember/pull/245) [avoid-leaking-state-in-ember-objects] Expose default ignored properties. ([@Kerrick](https://github.com/Kerrick))\n\n#### :memo: Documentation\n* [#208](https://github.com/ember-cli/eslint-plugin-ember/pull/208) Add URL to rule documentation to the metadata. ([@Arcanemagus](https://github.com/Arcanemagus))\n\n#### :house: Internal\n* [#142](https://github.com/ember-cli/eslint-plugin-ember/pull/142) Port code to ember-rfc176-data new format. ([@Serabe](https://github.com/Serabe))\n\n#### Committers: 3\n- Kerrick Long ([Kerrick](https://github.com/Kerrick))\n- Landon Abney ([Arcanemagus](https://github.com/Arcanemagus))\n- Sergio Arbeo ([Serabe](https://github.com/Serabe))\n\n\n## v5.1.1 (2018-05-14)\n\n#### :bug: Bug Fix\n* [#229](https://github.com/ember-cli/eslint-plugin-ember/pull/229) Fix no-capital-letters-in-routes so it deals with MemberExpressions. ([@nlfurniss](https://github.com/nlfurniss))\n\n#### :memo: Documentation\n* [#241](https://github.com/ember-cli/eslint-plugin-ember/pull/241) Removes the no-jquery doc typo. ([@thebluejay](https://github.com/thebluejay))\n\n#### :house: Internal\n* [#254](https://github.com/ember-cli/eslint-plugin-ember/pull/254) Drop require-folder-tree dependency. ([@rwjblue](https://github.com/rwjblue))\n* [#242](https://github.com/ember-cli/eslint-plugin-ember/pull/242) Update `jest` to v21.2.1. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 4\n- Nathaniel Furniss ([nlfurniss](https://github.com/nlfurniss))\n- Robert Jackson ([rwjblue](https://github.com/rwjblue))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n- [thebluejay](https://github.com/thebluejay)\n\n\n## v5.1.0 (2018-03-11)\n\n#### :rocket: Enhancement\n* [#172](https://github.com/ember-cli/eslint-plugin-ember/pull/172) Add `--fix` support to `order-in-*` rules. ([@PrzemoRevolve](https://github.com/PrzemoRevolve))\n\n#### :bug: Bug Fix\n* [#233](https://github.com/ember-cli/eslint-plugin-ember/pull/233) Fix init order in controllers and routes. ([@ro0gr](https://github.com/ro0gr))\n* [#198](https://github.com/ember-cli/eslint-plugin-ember/pull/198) Add new scenarios for `require-super-in-init` rule. ([@clcuevas](https://github.com/clcuevas))\n* [#205](https://github.com/ember-cli/eslint-plugin-ember/pull/205) add willInsertElement component lifecycle hook. ([@hakubo](https://github.com/hakubo))\n\n#### Committers: 8\n- Claudia Cuevas ([clcuevas](https://github.com/clcuevas))\n- Jakub Olek ([hakubo](https://github.com/hakubo))\n- Jason Williams ([jaswilli](https://github.com/jaswilli))\n- Przemysław Nowak ([PrzemoRevolve](https://github.com/PrzemoRevolve))\n- Ricardo Mendes ([locks](https://github.com/locks))\n- Ruslan Grabovoy ([ro0gr](https://github.com/ro0gr))\n- Sylvain MINA ([sly7-7](https://github.com/sly7-7))\n- [verim1990](https://github.com/verim1990)\n\n\n## v5.0.3 (2017-12-21)\n\n#### :bug: Bug Fix\n* [#197](https://github.com/ember-cli/eslint-plugin-ember/pull/197) Don't fail 'no-global-jquery' if module has both jquery and ember imports. ([@danwenzel](https://github.com/danwenzel))\n\n#### Committers: 1\n- Dan Wenzel ([danwenzel](https://github.com/danwenzel))\n\n## v5.0.2 (2017-12-18)\n\n#### :bug: Bug Fix\n* [#186](https://github.com/ember-cli/eslint-plugin-ember/pull/186) Update `no-global-jquery` rule to account for new modules import. ([@clcuevas](https://github.com/clcuevas))\n\n#### :memo: Documentation\n* [#194](https://github.com/ember-cli/eslint-plugin-ember/pull/194) Update README.md. ([@bartocc](https://github.com/bartocc))\n\n#### Committers: 2\n- Claudia Cuevas ([clcuevas](https://github.com/clcuevas))\n- Julien Palmas ([bartocc](https://github.com/bartocc))\n\n## v5.0.1 (2017-11-20)\n\n#### :rocket: Enhancement\n* [#144](https://github.com/ember-cli/eslint-plugin-ember/pull/144) Add destructuring support to `new-modules-import` rule. ([@clcuevas](https://github.com/clcuevas))\n* [#151](https://github.com/ember-cli/eslint-plugin-ember/pull/151) Allow recognition of controller injection. ([@rmachielse](https://github.com/rmachielse))\n\n#### :bug: Bug Fix\n* [#184](https://github.com/ember-cli/eslint-plugin-ember/pull/184) Prevent error when destructured path is not in known globals.. ([@rwjblue](https://github.com/rwjblue))\n\n#### :memo: Documentation\n* [#178](https://github.com/ember-cli/eslint-plugin-ember/pull/178) Add Release instructions. ([@Turbo87](https://github.com/Turbo87))\n* [#167](https://github.com/ember-cli/eslint-plugin-ember/pull/167) Added Yarn Install. ([@Alonski](https://github.com/Alonski))\n* [#177](https://github.com/ember-cli/eslint-plugin-ember/pull/177) Add CHANGELOG. ([@Turbo87](https://github.com/Turbo87))\n* [#176](https://github.com/ember-cli/eslint-plugin-ember/pull/176) Add myself to contributors. ([@jbandura](https://github.com/jbandura))\n\n#### Committers: 6\n- Alon Bukai ([Alonski](https://github.com/Alonski))\n- Claudia Cuevas ([clcuevas](https://github.com/clcuevas))\n- Jacek Bandura ([jbandura](https://github.com/jbandura))\n- [rmachielse](https://github.com/rmachielse)\n- Robert Jackson ([rwjblue](https://github.com/rwjblue))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n-\n## v5.0.0 (2017-11-20)\n\n* 📦 - Change `recommended` rule set to match `eslint` pattern of only including rules that prevent errors (and specifically excluding stylistic rules).\n  * ❌  - Remove `alias-model-in-controller` from `ember/recommended` rule set.\n  * ❌  - Remove `avoid-leaking-state-in-components` from `ember/recommended` rule set.\n  * ❌  - Remove `named-functions-in-promises` from `ember/recommended` rule set.\n  * ❌  - Remove `no-empty-attrs` from `ember/recommended` rule set.\n  * ❌  - Remove `no-observers` from `ember/recommended` rule set.\n  * ❌  - Remove `use-ember-get-and-set` from `ember/recommended` rule set.\n  * ❌  - Remove `order-in-components` from `ember/recommended` rule set.\n  * ❌  - Remove `order-in-controllers` from `ember/recommended` rule set.\n  * ❌  - Remove `order-in-models` from `ember/recommended` rule set.\n  * ❌  - Remove `order-in-routes` from `ember/recommended` rule set.\n  * ✅  - Add `avoid-leaking-state-in-ember-objects` to `ember/recommended` rule set.\n  * ✅  - Add `new-module-imports` to `ember/recommended` rule set.\n  * ✅  - Add `no-attrs-in-components` to `ember/recommended` rule set.\n  * ✅  - Add `no-duplicate-dependent-keys` from `ember/recommended` rule set.\n  * ✅  - Add `no-global-jquery` to `ember/recommended` rule set.\n  * ✅  - Add `no-old-shims` to `ember/recommended` rule set.\n  * ✅  - Add `require-super-in-init` to `ember/recommended` rule set.\n\n#### :boom: Breaking Change\n* [#173](https://github.com/ember-cli/eslint-plugin-ember/pull/173) Disable `order-in-*` rules by default. ([@Turbo87](https://github.com/Turbo87))\n* [#174](https://github.com/ember-cli/eslint-plugin-ember/pull/174) Disable and deprecate `avoid-leaking-state-in-components` rule. ([@Turbo87](https://github.com/Turbo87))\n* [#146](https://github.com/ember-cli/eslint-plugin-ember/pull/146) Update configs and recommendations. ([@michalsnik](https://github.com/michalsnik))\n\n#### :rocket: Enhancement\n* [#144](https://github.com/ember-cli/eslint-plugin-ember/pull/144) Add destructuring support to `new-modules-import` rule. ([@clcuevas](https://github.com/clcuevas))\n* [#151](https://github.com/ember-cli/eslint-plugin-ember/pull/151) Allow recognition of controller injection. ([@rmachielse](https://github.com/rmachielse))\n\n#### :memo: Documentation\n* [#178](https://github.com/ember-cli/eslint-plugin-ember/pull/178) Add Release instructions. ([@Turbo87](https://github.com/Turbo87))\n* [#167](https://github.com/ember-cli/eslint-plugin-ember/pull/167) Added Yarn Install. ([@Alonski](https://github.com/Alonski))\n* [#177](https://github.com/ember-cli/eslint-plugin-ember/pull/177) Add CHANGELOG. ([@Turbo87](https://github.com/Turbo87))\n* [#176](https://github.com/ember-cli/eslint-plugin-ember/pull/176) Add myself to contributors. ([@jbandura](https://github.com/jbandura))\n\n#### Committers: 9\n- Alon Bukai ([Alonski](https://github.com/Alonski))\n- Claudia Cuevas ([clcuevas](https://github.com/clcuevas))\n- Jacek Bandura ([jbandura](https://github.com/jbandura))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n- [rmachielse](https://github.com/rmachielse)\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v4.6.2 (2017-11-15)\n\n#### :rocket: Enhancement\n* [#173](https://github.com/ember-cli/eslint-plugin-ember/pull/173) Disable `order-in-*` rules by default. ([@Turbo87](https://github.com/Turbo87))\n* [#155](https://github.com/ember-cli/eslint-plugin-ember/pull/155) ember-get-and-set Add option `ignoreThisExpressions`. ([@JoelWAnna](https://github.com/JoelWAnna))\n* [#174](https://github.com/ember-cli/eslint-plugin-ember/pull/174) Disable and deprecate `avoid-leaking-state-in-components` rule. ([@Turbo87](https://github.com/Turbo87))\n* [#146](https://github.com/ember-cli/eslint-plugin-ember/pull/146) Update configs and recommendations. ([@michalsnik](https://github.com/michalsnik))\n\n#### :bug: Bug Fix\n* [#169](https://github.com/ember-cli/eslint-plugin-ember/pull/169) Closes [#150](https://github.com/ember-cli/eslint-plugin-ember/issues/150) issue with 'init' property. ([@eskab](https://github.com/eskab))\n* [#168](https://github.com/ember-cli/eslint-plugin-ember/pull/168) Fix lint rule crash when variable is used as routename. ([@cspanring](https://github.com/cspanring))\n* [#152](https://github.com/ember-cli/eslint-plugin-ember/pull/152) Detect models based on their files' path. ([@rmachielse](https://github.com/rmachielse))\n\n#### :memo: Documentation\n* [#175](https://github.com/ember-cli/eslint-plugin-ember/pull/175) Add deprecations to README. ([@Turbo87](https://github.com/Turbo87))\n* [#166](https://github.com/ember-cli/eslint-plugin-ember/pull/166) add missing \"plugins\" and reorder. ([@kellyselden](https://github.com/kellyselden))\n\n#### :house: Internal\n* [#171](https://github.com/ember-cli/eslint-plugin-ember/pull/171) Fix failing tests on windows due to path separators. ([@PrzemoRevolve](https://github.com/PrzemoRevolve))\n\n#### Committers: 9\n- Christian Spanring ([cspanring](https://github.com/cspanring))\n- Kelly Selden ([kellyselden](https://github.com/kellyselden))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n- Przemysław Nowak ([PrzemoRevolve](https://github.com/PrzemoRevolve))\n- [rmachielse](https://github.com/rmachielse)\n- Szymon Kabelis ([eskab](https://github.com/eskab))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n- [JoelWAnna](https://github.com/JoelWAnna)\n- [shegupta](https://github.com/shegupta)\n\n\n## v4.6.1 (2017-11-06)\n\n#### :bug: Bug Fix\n* [#160](https://github.com/ember-cli/eslint-plugin-ember/pull/160) ignore tagged templates for avoid-leaking-state-in-ember-objects rule. ([@amk221](https://github.com/amk221))\n\n#### Committers: 1\n- Andrew Kirwin ([amk221](https://github.com/amk221))\n\n\n## v4.6.0 (2017-11-03)\n\n#### :rocket: Enhancement\n* [#137](https://github.com/ember-cli/eslint-plugin-ember/pull/137) Enforce component lifecycle hook order. ([@rwwagner90](https://github.com/rwwagner90))\n\n#### :memo: Documentation\n* [#154](https://github.com/ember-cli/eslint-plugin-ember/pull/154) Add myself to contributors, use https for GitHub links. ([@rwwagner90](https://github.com/rwwagner90))\n* [#153](https://github.com/ember-cli/eslint-plugin-ember/pull/153) minor typo in readme. ([@zkrzyzanowski](https://github.com/zkrzyzanowski))\n\n#### Committers: 3\n- Robert Wagner ([rwwagner90](https://github.com/rwwagner90))\n- Zach Krzyzanowski ([zkrzyzanowski](https://github.com/zkrzyzanowski))\n- [shegupta](https://github.com/shegupta)\n\n\n## v4.5.0 (2017-09-02)\n\n#### :rocket: Enhancement\n* [#121](https://github.com/ember-cli/eslint-plugin-ember/pull/121) Add rule to disallow `this.$` to prepare apps to remove jQuery. ([@cibernox](https://github.com/cibernox))\n\n#### Committers: 1\n- Miguel Camba ([cibernox](https://github.com/cibernox))\n\n\n## v4.4.0 (2017-09-02)\n\n#### :rocket: Enhancement\n* [#124](https://github.com/ember-cli/eslint-plugin-ember/pull/124) Order beforeModel and afterModel around model. ([@rwwagner90](https://github.com/rwwagner90))\n* [#108](https://github.com/ember-cli/eslint-plugin-ember/pull/108) Add additional position for empty methods. ([@t-sauer](https://github.com/t-sauer))\n\n#### :bug: Bug Fix\n* [#132](https://github.com/ember-cli/eslint-plugin-ember/pull/132) Don't report on Ember method calls in use-ember-get-and-set. ([@sudowork](https://github.com/sudowork))\n\n#### :house: Internal\n* [#133](https://github.com/ember-cli/eslint-plugin-ember/pull/133) Add recommended rules snapshot test. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 4\n- Kevin Gao ([sudowork](https://github.com/sudowork))\n- Robert Wagner ([rwwagner90](https://github.com/rwwagner90))\n- Thomas Sauer ([t-sauer](https://github.com/t-sauer))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v4.3.0 (2017-08-15)\n\n#### :rocket: Enhancement\n* [#104](https://github.com/ember-cli/eslint-plugin-ember/pull/104) New rule: no-duplicate-dependent-keys. ([@jbandura](https://github.com/jbandura))\n* [#122](https://github.com/ember-cli/eslint-plugin-ember/pull/122) Add new \"call-super-in-init\" rule. ([@kevinkucharczyk](https://github.com/kevinkucharczyk))\n\n#### :bug: Bug Fix\n* [#107](https://github.com/ember-cli/eslint-plugin-ember/pull/107) Don't suggest nested property brace expansion. ([@Kerrick](https://github.com/Kerrick))\n\n#### :memo: Documentation\n* [#125](https://github.com/ember-cli/eslint-plugin-ember/pull/125) named-functions-in-promises example without .bind(). ([@caseywatts](https://github.com/caseywatts))\n* [#120](https://github.com/ember-cli/eslint-plugin-ember/pull/120) Fix broken links in readme. ([@dustinspecker](https://github.com/dustinspecker))\n\n#### :house: Internal\n* [#118](https://github.com/ember-cli/eslint-plugin-ember/pull/118) Update \"ember-rfc176-data\" to v0.2.7. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 6\n- Casey Watts ([caseywatts](https://github.com/caseywatts))\n- Dustin Specker ([dustinspecker](https://github.com/dustinspecker))\n- Jacek Bandura ([jbandura](https://github.com/jbandura))\n- Kerrick Long ([Kerrick](https://github.com/Kerrick))\n- Kevin Kucharczyk ([kevinkucharczyk](https://github.com/kevinkucharczyk))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v4.2.0 (2017-08-03)\n\n#### :rocket: Enhancement\n* [#115](https://github.com/ember-cli/eslint-plugin-ember/pull/115) Add `fix` for `use-ember-get-and-set`. ([@sudowork](https://github.com/sudowork))\n* [#116](https://github.com/ember-cli/eslint-plugin-ember/pull/116) Add \"new-module-imports\" rule. ([@Turbo87](https://github.com/Turbo87))\n\n#### :house: Internal\n* [#117](https://github.com/ember-cli/eslint-plugin-ember/pull/117) CI: Publish tags to npm automatically. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 2\n- Kevin Gao ([sudowork](https://github.com/sudowork))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v4.1.3 (2017-08-01)\n\n#### :bug: Bug Fix\n* [#114](https://github.com/ember-cli/eslint-plugin-ember/pull/114) Fix \"no-global-jquery\". ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 1\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v4.1.1 (2017-08-01)\n\n#### :rocket: Enhancement\n* [#103](https://github.com/ember-cli/eslint-plugin-ember/pull/103) New rule: no-global-jquery. ([@jbandura](https://github.com/jbandura))\n* [#100](https://github.com/ember-cli/eslint-plugin-ember/pull/100) New rule: no-attrs (Closes [#52](https://github.com/ember-cli/eslint-plugin-ember/issues/52)). ([@jbandura](https://github.com/jbandura))\n* [#99](https://github.com/ember-cli/eslint-plugin-ember/pull/99) Adding new rule: no-attrs-snapshot. ([@scalvert](https://github.com/scalvert))\n\n#### :bug: Bug Fix\n* [#111](https://github.com/ember-cli/eslint-plugin-ember/pull/111) Add solution for service ordering when new module imports used. ([@jbandura](https://github.com/jbandura))\n* [#98](https://github.com/ember-cli/eslint-plugin-ember/pull/98) Detecting computed properties with MemberExpressions. ([@jbandura](https://github.com/jbandura))\n\n#### :memo: Documentation\n* [#101](https://github.com/ember-cli/eslint-plugin-ember/pull/101) netguru -> ember-cli. ([@rwwagner90](https://github.com/rwwagner90))\n* [#92](https://github.com/ember-cli/eslint-plugin-ember/pull/92) Update README & introduce auto generated table with rules. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 4\n- Jacek Bandura ([jbandura](https://github.com/jbandura))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n- Robert Wagner ([rwwagner90](https://github.com/rwwagner90))\n- Steve Calvert ([scalvert](https://github.com/scalvert))\n\n\n## v3.6.2 (2017-07-13)\n\n#### :bug: Bug Fix\n* [#93](https://github.com/ember-cli/eslint-plugin-ember/pull/93) Make sure negative values are treated as properties. ([@jbandura](https://github.com/jbandura))\n\n#### Committers: 1\n- Jacek Bandura ([jbandura](https://github.com/jbandura))\n\n\n## v3.6.1 (2017-07-11)\n\n#### :bug: Bug Fix\n* [#94](https://github.com/ember-cli/eslint-plugin-ember/pull/94) Fix method of detecting whether route segment present. ([@jbandura](https://github.com/jbandura))\n\n#### Committers: 1\n- Jacek Bandura ([jbandura](https://github.com/jbandura))\n\n\n## v3.6.0 (2017-07-10)\n\n#### :rocket: Enhancement\n* [#90](https://github.com/ember-cli/eslint-plugin-ember/pull/90) Add new \"no-old-shims\" rule. ([@Turbo87](https://github.com/Turbo87))\n\n#### :house: Internal\n* [#91](https://github.com/ember-cli/eslint-plugin-ember/pull/91) Switch test runner from Mocha/Chai to Jest. ([@Turbo87](https://github.com/Turbo87))\n* [#89](https://github.com/ember-cli/eslint-plugin-ember/pull/89) Update \"yarn.lock\" file. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 1\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v3.5.0 (2017-06-26)\n\n#### :rocket: Enhancement\n* [#80](https://github.com/ember-cli/eslint-plugin-ember/pull/80) alias-model-in-controller: allow nested properties. ([@buschtoens](https://github.com/buschtoens))\n* [#77](https://github.com/ember-cli/eslint-plugin-ember/pull/77) Add no-capital-letters-in-routes to base. ([@scottkidder](https://github.com/scottkidder))\n\n#### Committers: 2\n- Jan Buschtöns ([buschtoens](https://github.com/buschtoens))\n- Scott Kidder ([scottkidder](https://github.com/scottkidder))\n\n\n## v3.4.1 (2017-05-30)\n\n#### :bug: Bug Fix\n* [#74](https://github.com/ember-cli/eslint-plugin-ember/pull/74) Revert \"Make it available on emberobserver.com\". ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 1\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v3.4.0 (2017-05-27)\n\n#### :rocket: Enhancement\n* [#70](https://github.com/ember-cli/eslint-plugin-ember/pull/70) New rule: no-capital-letters-in-routes. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 1\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v3.3.0 (2017-05-24)\n\n#### :rocket: Enhancement\n* [#64](https://github.com/ember-cli/eslint-plugin-ember/pull/64) Allow concise ArrowFunctionExpression (named-functions-in-promises). ([@sudowork](https://github.com/sudowork))\n* [#66](https://github.com/ember-cli/eslint-plugin-ember/pull/66) Support tagged templates expressions. ([@michalsnik](https://github.com/michalsnik))\n\n#### :memo: Documentation\n* [#43](https://github.com/ember-cli/eslint-plugin-ember/pull/43) Sync groups between desc and javascript snippet. ([@bartocc](https://github.com/bartocc))\n* [#65](https://github.com/ember-cli/eslint-plugin-ember/pull/65) Don't use this.attrs in docs. ([@sudowork](https://github.com/sudowork))\n\n#### Committers: 3\n- Julien Palmas ([bartocc](https://github.com/bartocc))\n- Kevin Gao ([sudowork](https://github.com/sudowork))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v3.2.0 (2017-05-23)\n\n#### :rocket: Enhancement\n* [#60](https://github.com/ember-cli/eslint-plugin-ember/pull/60) `alias-model-in-controller` should support `readOnly` and `reads`. ([@michalsnik](https://github.com/michalsnik))\n* [#46](https://github.com/ember-cli/eslint-plugin-ember/pull/46) Fix typo in documentation: rename 'no-side-effect' to 'no-side-effects'. ([@RusPosevkin](https://github.com/RusPosevkin))\n\n#### :memo: Documentation\n* [#50](https://github.com/ember-cli/eslint-plugin-ember/pull/50) Add syntax highlighting to Readme. ([@ryanponce](https://github.com/ryanponce))\n* [#49](https://github.com/ember-cli/eslint-plugin-ember/pull/49) Update docs. ([@michalsnik](https://github.com/michalsnik))\n\n#### :house: Internal\n* [#54](https://github.com/ember-cli/eslint-plugin-ember/pull/54) Update rules to new ESLint rule format. ([@SaladFork](https://github.com/SaladFork))\n\n#### Committers: 4\n- Elad Shahar ([SaladFork](https://github.com/SaladFork))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n- Ruslan Posevkin ([RusPosevkin](https://github.com/RusPosevkin))\n- Ryan Ponce ([ryanponce](https://github.com/ryanponce))\n\n\n## v3.1.2 (2017-03-24)\n\n#### :rocket: Enhancement\n* [#44](https://github.com/ember-cli/eslint-plugin-ember/pull/44) Update no-on-calls-in-components rule. ([@clcuevas](https://github.com/clcuevas))\n\n#### :memo: Documentation\n* [#40](https://github.com/ember-cli/eslint-plugin-ember/pull/40) Fix configuration key. ([@bdmac](https://github.com/bdmac))\n\n#### Committers: 2\n- Brian McManus ([bdmac](https://github.com/bdmac))\n- Claudia Cuevas ([clcuevas](https://github.com/clcuevas))\n\n\n## v3.1.1 (2017-03-16)\n\n#### :rocket: Enhancement\n* [#39](https://github.com/ember-cli/eslint-plugin-ember/pull/39) Update no-empty-attrs rule. ([@clcuevas](https://github.com/clcuevas))\n\n#### Committers: 1\n- Claudia Cuevas ([clcuevas](https://github.com/clcuevas))\n\n\n## v3.1.0 (2017-03-16)\n\n#### :rocket: Enhancement\n* [#37](https://github.com/ember-cli/eslint-plugin-ember/pull/37) 27 / Detect module types based on their files' path. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 1\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v3.0.2 (2017-03-08)\n\n#### :rocket: Enhancement\n* [#38](https://github.com/ember-cli/eslint-plugin-ember/pull/38) Add functions to route order. ([@netes](https://github.com/netes))\n\n#### :memo: Documentation\n* [#35](https://github.com/ember-cli/eslint-plugin-ember/pull/35) Typo in docs / closure-actions.md. ([@nfc036](https://github.com/nfc036))\n* [#36](https://github.com/ember-cli/eslint-plugin-ember/pull/36) Remove remaining references to \"query-params-on-top\" rule. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 3\n- Kamil Ejsymont ([netes](https://github.com/netes))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n- [nfc036](https://github.com/nfc036)\n\n\n## v3.0.0 (2017-02-20)\n\n#### :rocket: Enhancement\n* [#34](https://github.com/ember-cli/eslint-plugin-ember/pull/34) Improve order in controllers and documentation. ([@michalsnik](https://github.com/michalsnik))\n\n#### :house: Internal\n* [#33](https://github.com/ember-cli/eslint-plugin-ember/pull/33) Add eslint and use ES6. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 1\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v2.2.2 (2017-02-15)\n\n#### :rocket: Enhancement\n* [#31](https://github.com/ember-cli/eslint-plugin-ember/pull/31) Treat conditional expressions as custom properties. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 1\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v2.2.1 (2017-02-15)\n\n#### :bug: Bug Fix\n* [#30](https://github.com/ember-cli/eslint-plugin-ember/pull/30) Check only model's properties against `no-empty-attrs` rule. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 1\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v2.2.0 (2017-02-14)\n\n#### :rocket: Enhancement\n* [#23](https://github.com/ember-cli/eslint-plugin-ember/pull/23) Improved error messages for `order-in` rules. ([@Turbo87](https://github.com/Turbo87))\n\n#### Committers: 1\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v2.1.1 (2017-02-07)\n\n#### :bug: Bug Fix\n* [#16](https://github.com/ember-cli/eslint-plugin-ember/pull/16) Fix named-functions-in-promises rule. ([@michalsnik](https://github.com/michalsnik))\n\n#### :memo: Documentation\n* [#22](https://github.com/ember-cli/eslint-plugin-ember/pull/22) name correction. ([@bcardarella](https://github.com/bcardarella))\n\n#### Committers: 2\n- Brian Cardarella ([bcardarella](https://github.com/bcardarella))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n\n\n## v2.1.0 (2017-02-05)\n\n#### :rocket: Enhancement\n* [#15](https://github.com/ember-cli/eslint-plugin-ember/pull/15) Report correct positions for \"order-in-*\" rules. ([@Turbo87](https://github.com/Turbo87))\n\n#### :memo: Documentation\n* [#20](https://github.com/ember-cli/eslint-plugin-ember/pull/20) Update README and contributors. ([@michalsnik](https://github.com/michalsnik))\n* [#12](https://github.com/ember-cli/eslint-plugin-ember/pull/12) doc: Adjust `no-side-effects` name and link. ([@Turbo87](https://github.com/Turbo87))\n* [#7](https://github.com/ember-cli/eslint-plugin-ember/pull/7) README: Remove `ember-cli-eslint` requirement. ([@bardzusny](https://github.com/bardzusny))\n\n#### :house: Internal\n* [#19](https://github.com/ember-cli/eslint-plugin-ember/pull/19) Describe, launch tests under all supported Node.js versions. ([@bardzusny](https://github.com/bardzusny))\n* [#9](https://github.com/ember-cli/eslint-plugin-ember/pull/9) Disable nyan reporter in CI. ([@alexlafroscia](https://github.com/alexlafroscia))\n* [#8](https://github.com/ember-cli/eslint-plugin-ember/pull/8) Match yeoman generator structure. ([@alexlafroscia](https://github.com/alexlafroscia))\n* [#4](https://github.com/ember-cli/eslint-plugin-ember/pull/4) Chore / Update plugin environment. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 4\n- Adrian ([bardzusny](https://github.com/bardzusny))\n- Alex LaFroscia ([alexlafroscia](https://github.com/alexlafroscia))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n\n\n## v2.0.1 (2017-01-16)\n\n#### :bug: Bug Fix\n* [#3](https://github.com/ember-cli/eslint-plugin-ember/pull/3) Fix error in 'use-brace-expansion' rule. ([@dwickern](https://github.com/dwickern))\n\n#### Committers: 1\n- Derek Wickern ([dwickern](https://github.com/dwickern))\n\n\n## v2.0.0 (2016-12-30)\n\n#### :rocket: Enhancement\n* [#1](https://github.com/ember-cli/eslint-plugin-ember/pull/1) Add base configurations. ([@michalsnik](https://github.com/michalsnik))\n* [#34](https://github.com/ember-cli/eslint-plugin-ember/pull/34) Improve order in controllers and documentation. ([@michalsnik](https://github.com/michalsnik))\n* [#37](https://github.com/ember-cli/eslint-plugin-ember/pull/37) 27 / Detect module types based on their files' path. ([@michalsnik](https://github.com/michalsnik))\n* [#31](https://github.com/ember-cli/eslint-plugin-ember/pull/31) Treat conditional expressions as custom properties. ([@michalsnik](https://github.com/michalsnik))\n* [#23](https://github.com/ember-cli/eslint-plugin-ember/pull/23) Improved error messages for `order-in` rules. ([@Turbo87](https://github.com/Turbo87))\n* [#15](https://github.com/ember-cli/eslint-plugin-ember/pull/15) Report correct positions for \"order-in-*\" rules. ([@Turbo87](https://github.com/Turbo87))\n\n#### :bug: Bug Fix\n* False positive for `no-empty-attrs`. ([@bdmac](https://github.com/bdmac))\n* order-in-controllers vs query-params-on-top. ([@michalsnik](https://github.com/michalsnik))\n* [#16](https://github.com/ember-cli/eslint-plugin-ember/pull/16) Fix named-functions-in-promises rule. ([@michalsnik](https://github.com/michalsnik))\n\n#### :memo: Documentation\n* [#35](https://github.com/ember-cli/eslint-plugin-ember/pull/35) Typo in docs / closure-actions.md. ([@nfc036](https://github.com/nfc036))\n* [#22](https://github.com/ember-cli/eslint-plugin-ember/pull/22) name correction. ([@bcardarella](https://github.com/bcardarella))\n* [#20](https://github.com/ember-cli/eslint-plugin-ember/pull/20) Update README and contributors. ([@michalsnik](https://github.com/michalsnik))\n\n#### :house: Internal\n* [#33](https://github.com/ember-cli/eslint-plugin-ember/pull/33) Add eslint and use ES6. ([@michalsnik](https://github.com/michalsnik))\n* [#19](https://github.com/ember-cli/eslint-plugin-ember/pull/19) Describe, launch tests under all supported Node.js versions. ([@bardzusny](https://github.com/bardzusny))\n* [#4](https://github.com/ember-cli/eslint-plugin-ember/pull/4) Chore / Update plugin environment. ([@michalsnik](https://github.com/michalsnik))\n\n#### Committers: 9\n- Adrian ([bardzusny](https://github.com/bardzusny))\n- Brian Cardarella ([bcardarella](https://github.com/bcardarella))\n- Brian McManus ([bdmac](https://github.com/bdmac))\n- Craig Bilner ([craigbilner](https://github.com/craigbilner))\n- Kamil Ejsymont ([netes](https://github.com/netes))\n- Marcin Horoszko ([cinkonaap](https://github.com/cinkonaap))\n- Michał Sajnóg ([michalsnik](https://github.com/michalsnik))\n- Tobias Bieniek ([Turbo87](https://github.com/Turbo87))\n- [nfc036](https://github.com/nfc036)\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright 2016-2017 Netguru Sp. z o.o.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# eslint-plugin-ember\n\n[![NPM version](https://img.shields.io/npm/v/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember)\n[![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-ember.svg?style=flat)](https://npmjs.org/package/eslint-plugin-ember)\n![CI](https://github.com/ember-cli/eslint-plugin-ember/workflows/CI/badge.svg)\n\n> An ESLint plugin that provides a set of rules for Ember applications based on commonly known good practices.\n\n## ❗️Requirements\n\n- [ESLint](https://eslint.org/) `>= 8.40.0`\n- [Node.js](https://nodejs.org/) `>= 20.19`\n\n## 🚀 Usage\n\n### 1. Install plugin\n\n```shell\nnpm install --save-dev eslint-plugin-ember\n```\n\n### 2. Update your config\n\n```js\n// eslint.config.js (flat config)\nconst eslintPluginEmberRecommended = require('eslint-plugin-ember/configs/recommended');\n\nmodule.exports = [\n  ...eslintPluginEmberRecommended,\n];\n```\n\nor\n\n```js\n// .eslintrc.js (legacy config)\nmodule.exports = {\n  plugins: ['ember'],\n  extends: [\n    'eslint:recommended',\n    'plugin:ember/recommended' // or other configuration\n  ],\n  rules: {\n    // override / enable optional rules\n    'ember/no-replace-test-comments': 'error'\n  }\n};\n```\n\n## gts/gjs\n\nlint files having `First-Class Component Templates` (fcct)\n\nlearn more at [ember-template-imports](https://github.com/ember-template-imports/ember-template-imports)\n\n> [!NOTE]\n> special care should be used when setting up parsers, since they cannot be overwritten. thus they should be used in override only and specific to file types\n\ngjs/gts support is provided by the [ember-eslint-parser](https://github.com/NullVoxPopuli/ember-eslint-parser)\n\n> [!NOTE]\n> if you import .gts files in .ts files, then `ember-eslint-parser` is required for .ts as well to enable typed linting\n\n```js\n// .eslintrc.js\nmodule.exports = {\n  overrides: [\n    {\n      files: ['**/*.{js,ts}'],\n      plugins: ['ember'],\n      parser: '@typescript-eslint/parser',\n      extends: [\n        'eslint:recommended',\n        'plugin:ember/recommended', // or other configuration\n      ],\n      rules: {\n        // override / enable optional rules\n        'ember/no-replace-test-comments': 'error'\n      }\n    },\n    {\n      files: ['**/*.gts'],\n      parser: 'ember-eslint-parser',\n      plugins: ['ember'],\n      extends: [\n        'eslint:recommended',\n        'plugin:@typescript-eslint/recommended',\n        'plugin:ember/recommended',\n        'plugin:ember/recommended-gts',\n      ],\n    },\n    {\n      files: ['**/*.gjs'],\n      parser: 'ember-eslint-parser',\n      plugins: ['ember'],\n      extends: [\n        'eslint:recommended',\n        'plugin:ember/recommended',\n        'plugin:ember/recommended-gjs',\n      ],\n    },\n    {\n      files: ['tests/**/*.{js,ts,gjs,gts}'],\n      rules: {\n        // override / enable optional rules\n        'ember/no-replace-test-comments': 'error'\n      }\n    },\n  ],\n};\n```\n\n### rules applied to fcct templates\n\n- semi rule, same as [prettier plugin](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1)\n- no-undef rule will take effect for template vars (includes js scope)\n- no-unused rule will take effect for template block params\n\nrules in templates can be disabled with eslint directives with mustache or html comments:\n\n```hbs\n<template>\n  <div>\n    {{!eslint-disable-next-line}}\n    {{test}}\n  </div>\n  <div>\n    {{!--eslint-disable--}}\n    {{test}}\n    {{test}}\n    {{test}}\n    {{!--eslint-enable--}}\n  </div>\n</template>\n```\n\n```hbs\n<template>\n  <div>\n    <!--eslint-disable-next-line-->\n    {{test}}\n  </div>\n  <div>\n    <!-- eslint-disable -->\n    {{test}}\n    {{test}}\n    {{test}}\n    <!-- eslint-enable -->\n  </div>\n</template>\n```\n\n## Migrating from ember-template-lint\n\nIf you are replacing `ember-template-lint` with this plugin, use the `template-lint-migration` config to get the equivalent set of rules:\n\n```js\n// eslint.config.js (flat config)\nimport eslintPluginEmberRecommended from 'eslint-plugin-ember/configs/recommended';\nimport eslintPluginEmberTemplateLintMigration from 'eslint-plugin-ember/configs/template-lint-migration';\n\nexport default [\n  ...eslintPluginEmberRecommended,\n  ...eslintPluginEmberTemplateLintMigration,\n  {\n    rules: {\n      // Migrate custom overrides from your .template-lintrc.*:\n      'ember/template-no-bare-strings': ['error', { allowlist: ['×'] }],\n      'ember/template-no-inline-styles': 'off',\n    },\n  },\n];\n```\n\n`template-lint-migration` mirrors the ember-template-lint `recommended` preset.\n\n### Linting `.hbs` files\n\nESLint flat config only picks up `.hbs` files when a `files` glob names them. Add a dedicated block so they route to the Handlebars parser:\n\n```js\n// eslint.config.mjs\nimport { hbsParser, plugin as emberPlugin } from 'eslint-plugin-ember/recommended';\n\nexport default [\n  // ...other blocks...\n  {\n    files: ['app/**/*.hbs'],\n    languageOptions: { parser: hbsParser },\n    plugins: { ember: emberPlugin },\n    rules: {\n      'ember/template-no-bare-strings': 'error',\n      // ...other template rules...\n    },\n  },\n];\n```\n\nMake sure no earlier `@typescript-eslint/parser` block's `files` glob reaches `.hbs` — narrow it to `['**/*.{js,ts,gjs,gts}']` (or similar). Flat config merges rules across every matching block, so even if our HBS block overrides the parser, type-info rules from a matching TS block still layer on and fail with errors like:\n\n> Parsing error: `…` was not found by the project service because the extension for the file (`.hbs`) is non-standard.\n\nor\n\n> Error while loading rule `@typescript-eslint/await-thenable`: You have used a rule which requires type information.\n\n### Replacing `template-lint-disable` comments\n\nInline disable directives need to be rewritten to ESLint's syntax, prefixed with `ember/template-`. For now, only two scopes are supported: the next line, or the rest of the file. For example, replace:\n\n```hbs\n{{!template-lint-disable no-invalid-role}}\n```\n\nwith:\n\n```hbs\n{{!eslint-disable-next-line ember/template-no-invalid-role}}\n```\n\nTo disable a rule for an entire `.gjs`/`.gts` file, use a regular ESLint file-level directive in the JS region — it applies to the `<template>` contents as well:\n\n```gjs\n/* eslint-disable ember/template-no-invalid-role */\n\n<template>\n  <div role=\"range\"></div>\n</template>\n```\n\n## 🧰 Configurations\n\n<!-- begin auto-generated configs list -->\n\n|                                 | Name                      |\n| :------------------------------ | :------------------------ |\n|                                 | `base`                    |\n| ✅                               | `recommended`             |\n| ![gjs logo](/docs/svgs/gjs.svg) | `recommended-gjs`         |\n| ![gts logo](/docs/svgs/gts.svg) | `recommended-gts`         |\n| 📋                              | `template-lint-migration` |\n\n<!-- end auto-generated configs list -->\n\n## 🍟 Rules\n\n<!-- begin auto-generated rules list -->\n\n💼 [Configurations](https://github.com/ember-cli/eslint-plugin-ember#-configurations) enabled in.\\\n✅ Set in the `recommended` [configuration](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\\\n![gjs logo](/docs/svgs/gjs.svg) Set in the `recommended-gjs` [configuration](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\\\n![gts logo](/docs/svgs/gts.svg) Set in the `recommended-gts` [configuration](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\\\n📋 Set in the `template-lint-migration` [configuration](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\\\n🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\\\n💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n### Accessibility\n\n| Name                                                                                                             | Description                                                                      | 💼 | 🔧 | 💡 |\n| :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | :- | :- | :- |\n| [template-link-href-attributes](docs/rules/template-link-href-attributes.md)                                     | require href attribute on link elements                                          | 📋 |    |    |\n| [template-no-abstract-roles](docs/rules/template-no-abstract-roles.md)                                           | disallow abstract ARIA roles                                                     | 📋 |    |    |\n| [template-no-accesskey-attribute](docs/rules/template-no-accesskey-attribute.md)                                 | disallow accesskey attribute                                                     | 📋 | 🔧 |    |\n| [template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md)                                       | disallow aria-hidden on body element                                             | 📋 | 🔧 |    |\n| [template-no-aria-unsupported-elements](docs/rules/template-no-aria-unsupported-elements.md)                     | disallow ARIA roles, states, and properties on elements that do not support them | 📋 |    |    |\n| [template-no-autofocus-attribute](docs/rules/template-no-autofocus-attribute.md)                                 | disallow autofocus attribute                                                     | 📋 | 🔧 |    |\n| [template-no-duplicate-landmark-elements](docs/rules/template-no-duplicate-landmark-elements.md)                 | disallow duplicate landmark elements without unique labels                       | 📋 |    |    |\n| [template-no-empty-headings](docs/rules/template-no-empty-headings.md)                                           | disallow empty heading elements                                                  | 📋 |    |    |\n| [template-no-heading-inside-button](docs/rules/template-no-heading-inside-button.md)                             | disallow heading elements inside button elements                                 | 📋 |    |    |\n| [template-no-invalid-aria-attributes](docs/rules/template-no-invalid-aria-attributes.md)                         | disallow invalid aria-* attributes                                               | 📋 |    |    |\n| [template-no-invalid-interactive](docs/rules/template-no-invalid-interactive.md)                                 | disallow non-interactive elements with interactive handlers                      | 📋 |    |    |\n| [template-no-invalid-link-text](docs/rules/template-no-invalid-link-text.md)                                     | disallow invalid or uninformative link text content                              | 📋 |    |    |\n| [template-no-invalid-link-title](docs/rules/template-no-invalid-link-title.md)                                   | disallow invalid title attributes on link elements                               | 📋 |    |    |\n| [template-no-invalid-role](docs/rules/template-no-invalid-role.md)                                               | disallow invalid ARIA roles                                                      | 📋 |    |    |\n| [template-no-nested-interactive](docs/rules/template-no-nested-interactive.md)                                   | disallow nested interactive elements                                             | 📋 |    |    |\n| [template-no-nested-landmark](docs/rules/template-no-nested-landmark.md)                                         | disallow nested landmark elements                                                | 📋 |    |    |\n| [template-no-pointer-down-event-binding](docs/rules/template-no-pointer-down-event-binding.md)                   | disallow pointer down event bindings                                             | 📋 |    |    |\n| [template-no-positive-tabindex](docs/rules/template-no-positive-tabindex.md)                                     | disallow positive tabindex values                                                | 📋 |    |    |\n| [template-no-redundant-role](docs/rules/template-no-redundant-role.md)                                           | disallow redundant role attributes                                               | 📋 | 🔧 |    |\n| [template-no-unsupported-role-attributes](docs/rules/template-no-unsupported-role-attributes.md)                 | disallow ARIA attributes that are not supported by the element role              | 📋 | 🔧 |    |\n| [template-no-whitespace-within-word](docs/rules/template-no-whitespace-within-word.md)                           | disallow excess whitespace within words (e.g. \"W e l c o m e\")                   | 📋 |    |    |\n| [template-require-aria-activedescendant-tabindex](docs/rules/template-require-aria-activedescendant-tabindex.md) | require non-interactive elements with aria-activedescendant to have tabindex     | 📋 | 🔧 |    |\n| [template-require-context-role](docs/rules/template-require-context-role.md)                                     | require ARIA roles to be used in appropriate context                             | 📋 |    |    |\n| [template-require-iframe-title](docs/rules/template-require-iframe-title.md)                                     | require iframe elements to have a title attribute                                | 📋 |    |    |\n| [template-require-input-label](docs/rules/template-require-input-label.md)                                       | require label for form input elements                                            | 📋 |    |    |\n| [template-require-lang-attribute](docs/rules/template-require-lang-attribute.md)                                 | require lang attribute on html element                                           | 📋 |    |    |\n| [template-require-mandatory-role-attributes](docs/rules/template-require-mandatory-role-attributes.md)           | require mandatory ARIA attributes for ARIA roles                                 | 📋 |    |    |\n| [template-require-media-caption](docs/rules/template-require-media-caption.md)                                   | require captions for audio and video elements                                    | 📋 |    |    |\n| [template-require-presentational-children](docs/rules/template-require-presentational-children.md)               | require presentational elements to only contain presentational children          | 📋 |    |    |\n| [template-require-valid-alt-text](docs/rules/template-require-valid-alt-text.md)                                 | require valid alt text for images and other elements                             | 📋 |    |    |\n| [template-require-valid-form-groups](docs/rules/template-require-valid-form-groups.md)                           | require grouped form controls to have fieldset/legend or WAI-ARIA group labeling |    |    |    |\n| [template-table-groups](docs/rules/template-table-groups.md)                                                     | require table elements to use table grouping elements                            | 📋 |    |    |\n\n### Best Practices\n\n| Name                                                                                                               | Description                                                                  | 💼 | 🔧 | 💡 |\n| :----------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :- | :- | :- |\n| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md)                         | disallow setting certain attributes on builtin components                    | 📋 |    |    |\n| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md)                                         | disallow usage of {{action}} modifiers                                       |    | 🔧 |    |\n| [template-no-action-on-submit-button](docs/rules/template-no-action-on-submit-button.md)                           | disallow action attribute on submit buttons                                  | 📋 |    |    |\n| [template-no-args-paths](docs/rules/template-no-args-paths.md)                                                     | disallow args.foo paths in templates, use @foo instead                       | 📋 | 🔧 |    |\n| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md)                   | disallow @arguments on HTML elements                                         | 📋 |    |    |\n| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md)                     | disallow usage of Ember Array prototype extensions                           | 📋 | 🔧 |    |\n| [template-no-at-ember-render-modifiers](docs/rules/template-no-at-ember-render-modifiers.md)                       | disallow usage of @ember/render-modifiers                                    | 📋 |    |    |\n| [template-no-bare-strings](docs/rules/template-no-bare-strings.md)                                                 | disallow bare strings in templates (require translation/localization)        |    |    |    |\n| [template-no-bare-yield](docs/rules/template-no-bare-yield.md)                                                     | disallow templates whose only meaningful content is a bare {{yield}}         |    |    |    |\n| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md)             | disallow block params on HTML elements                                       | 📋 |    |    |\n| [template-no-builtin-form-components](docs/rules/template-no-builtin-form-components.md)                           | disallow usage of built-in form components                                   | 📋 |    |    |\n| [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md)                                       | disallow capital arguments (use lowercase @arg instead of @Arg)              | 📋 |    |    |\n| [template-no-chained-this](docs/rules/template-no-chained-this.md)                                                 | disallow redundant `this.this` in templates                                  |    | 🔧 |    |\n| [template-no-class-bindings](docs/rules/template-no-class-bindings.md)                                             | disallow passing classBinding or classNameBindings as arguments in templates | 📋 |    |    |\n| [template-no-curly-component-invocation](docs/rules/template-no-curly-component-invocation.md)                     | disallow curly component invocation, use angle bracket syntax instead        | 📋 | 🔧 |    |\n| [template-no-debugger](docs/rules/template-no-debugger.md)                                                         | disallow {{debugger}} in templates                                           | 📋 |    |    |\n| [template-no-duplicate-attributes](docs/rules/template-no-duplicate-attributes.md)                                 | disallow duplicate attribute names in templates                              | 📋 | 🔧 |    |\n| [template-no-duplicate-id](docs/rules/template-no-duplicate-id.md)                                                 | disallow duplicate id attributes                                             | 📋 |    |    |\n| [template-no-dynamic-subexpression-invocations](docs/rules/template-no-dynamic-subexpression-invocations.md)       | disallow dynamic subexpression invocations                                   |    |    |    |\n| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md)                               | disallow element event actions (use {{on}} modifier instead)                 |    |    |    |\n| [template-no-forbidden-elements](docs/rules/template-no-forbidden-elements.md)                                     | disallow specific HTML elements                                              | 📋 |    |    |\n| [template-no-html-comments](docs/rules/template-no-html-comments.md)                                               | disallow HTML comments in templates                                          | 📋 | 🔧 |    |\n| [template-no-implicit-this](docs/rules/template-no-implicit-this.md)                                               | require explicit `this` in property access                                   | 📋 |    |    |\n| [template-no-index-component-invocation](docs/rules/template-no-index-component-invocation.md)                     | disallow index component invocations                                         | 📋 |    |    |\n| [template-no-inline-event-handlers](docs/rules/template-no-inline-event-handlers.md)                               | disallow DOM event handler attributes                                        |    |    |    |\n| [template-no-inline-linkto](docs/rules/template-no-inline-linkto.md)                                               | disallow inline form of LinkTo component                                     |    | 🔧 |    |\n| [template-no-inline-styles](docs/rules/template-no-inline-styles.md)                                               | disallow inline styles                                                       | 📋 |    |    |\n| [template-no-input-block](docs/rules/template-no-input-block.md)                                                   | disallow block usage of {{input}} helper                                     | 📋 |    |    |\n| [template-no-input-tagname](docs/rules/template-no-input-tagname.md)                                               | disallow tagName attribute on {{input}} helper                               | 📋 |    |    |\n| [template-no-invalid-meta](docs/rules/template-no-invalid-meta.md)                                                 | disallow invalid meta tags                                                   | 📋 |    |    |\n| [template-no-log](docs/rules/template-no-log.md)                                                                   | disallow {{log}} in templates                                                | 📋 |    |    |\n| [template-no-model-argument-in-route-templates](docs/rules/template-no-model-argument-in-route-templates.md)       | disallow @model argument in route templates                                  |    | 🔧 |    |\n| [template-no-multiple-empty-lines](docs/rules/template-no-multiple-empty-lines.md)                                 | disallow multiple consecutive empty lines in templates                       |    | 🔧 |    |\n| [template-no-mut-helper](docs/rules/template-no-mut-helper.md)                                                     | disallow usage of (mut) helper                                               |    |    |    |\n| [template-no-negated-condition](docs/rules/template-no-negated-condition.md)                                       | disallow negated conditions in if/unless                                     | 📋 | 🔧 |    |\n| [template-no-nested-splattributes](docs/rules/template-no-nested-splattributes.md)                                 | disallow nested ...attributes usage                                          | 📋 |    |    |\n| [template-no-obscure-array-access](docs/rules/template-no-obscure-array-access.md)                                 | disallow obscure array access patterns like `objectPath.@each.property`      | 📋 | 🔧 |    |\n| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md)                                       | disallow obsolete HTML elements                                              | 📋 |    |    |\n| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md)                               | disallow {{outlet}} outside of route templates                               | 📋 |    |    |\n| [template-no-page-title-component](docs/rules/template-no-page-title-component.md)                                 | disallow usage of ember-page-title component                                 |    |    |    |\n| [template-no-passed-in-event-handlers](docs/rules/template-no-passed-in-event-handlers.md)                         | disallow passing event handlers directly as component arguments              | 📋 |    |    |\n| [template-no-positional-data-test-selectors](docs/rules/template-no-positional-data-test-selectors.md)             | disallow positional data-test-* params in curly invocations                  | 📋 | 🔧 |    |\n| [template-no-potential-path-strings](docs/rules/template-no-potential-path-strings.md)                             | disallow potential path strings in attribute values                          | 📋 |    |    |\n| [template-no-redundant-fn](docs/rules/template-no-redundant-fn.md)                                                 | disallow unnecessary usage of (fn) helper                                    | 📋 | 🔧 |    |\n| [template-no-restricted-invocations](docs/rules/template-no-restricted-invocations.md)                             | disallow certain components, helpers or modifiers from being used            |    |    |    |\n| [template-no-splattributes-with-class](docs/rules/template-no-splattributes-with-class.md)                         | disallow splattributes with class attribute                                  |    |    |    |\n| [template-no-this-in-template-only-components](docs/rules/template-no-this-in-template-only-components.md)         | disallow this in template-only components                                    |    | 🔧 |    |\n| [template-no-trailing-spaces](docs/rules/template-no-trailing-spaces.md)                                           | disallow trailing whitespace at the end of lines in templates                |    | 🔧 |    |\n| [template-no-unavailable-this](docs/rules/template-no-unavailable-this.md)                                         | disallow `this` in templates that are not inside a class or function         |    |    |    |\n| [template-no-unnecessary-component-helper](docs/rules/template-no-unnecessary-component-helper.md)                 | disallow unnecessary component helper                                        | 📋 | 🔧 |    |\n| [template-no-unnecessary-concat](docs/rules/template-no-unnecessary-concat.md)                                     | disallow unnecessary string concatenation                                    |    | 🔧 |    |\n| [template-no-unnecessary-curly-parens](docs/rules/template-no-unnecessary-curly-parens.md)                         | disallow unnecessary parentheses enclosing statements in curlies             | 📋 | 🔧 |    |\n| [template-no-unused-block-params](docs/rules/template-no-unused-block-params.md)                                   | disallow unused block parameters in templates                                | 📋 |    |    |\n| [template-no-valueless-arguments](docs/rules/template-no-valueless-arguments.md)                                   | disallow valueless named arguments                                           | 📋 |    |    |\n| [template-no-whitespace-for-layout](docs/rules/template-no-whitespace-for-layout.md)                               | disallow using whitespace for layout purposes                                | 📋 |    |    |\n| [template-no-yield-block-params-to-else-inverse](docs/rules/template-no-yield-block-params-to-else-inverse.md)     | disallow yielding block params to else or inverse block                      |    |    |    |\n| [template-no-yield-only](docs/rules/template-no-yield-only.md)                                                     | disallow components that only yield                                          | 📋 |    |    |\n| [template-no-yield-to-default](docs/rules/template-no-yield-to-default.md)                                         | disallow yield to default block                                              | 📋 |    |    |\n| [template-require-button-type](docs/rules/template-require-button-type.md)                                         | require button elements to have a valid type attribute                       | 📋 | 🔧 |    |\n| [template-require-each-key](docs/rules/template-require-each-key.md)                                               | require key attribute in {{#each}} loops                                     |    | 🔧 |    |\n| [template-require-form-method](docs/rules/template-require-form-method.md)                                         | require form method attribute                                                |    | 🔧 |    |\n| [template-require-has-block-helper](docs/rules/template-require-has-block-helper.md)                               | require (has-block) helper usage instead of hasBlock property                | 📋 | 🔧 |    |\n| [template-require-iframe-src-attribute](docs/rules/template-require-iframe-src-attribute.md)                       | require iframe elements to have src attribute                                |    | 🔧 |    |\n| [template-require-input-type](docs/rules/template-require-input-type.md)                                           | require input elements to have a valid type attribute                        |    | 🔧 |    |\n| [template-require-splattributes](docs/rules/template-require-splattributes.md)                                     | require splattributes usage in component templates                           |    |    |    |\n| [template-require-strict-mode](docs/rules/template-require-strict-mode.md)                                         | require templates to be in strict mode                                       |    |    |    |\n| [template-require-valid-named-block-naming-format](docs/rules/template-require-valid-named-block-naming-format.md) | require valid named block naming format                                      | 📋 | 🔧 |    |\n| [template-self-closing-void-elements](docs/rules/template-self-closing-void-elements.md)                           | require self-closing on void elements                                        |    | 🔧 |    |\n| [template-simple-modifiers](docs/rules/template-simple-modifiers.md)                                               | require simple modifier syntax                                               | 📋 |    |    |\n| [template-simple-unless](docs/rules/template-simple-unless.md)                                                     | require simple conditions in unless blocks                                   | 📋 | 🔧 |    |\n| [template-sort-invocations](docs/rules/template-sort-invocations.md)                                               | require sorted attributes and modifiers                                      |    | 🔧 |    |\n| [template-splat-attributes-only](docs/rules/template-splat-attributes-only.md)                                     | disallow ...spread other than ...attributes                                  | 📋 |    |    |\n| [template-style-concatenation](docs/rules/template-style-concatenation.md)                                         | disallow string concatenation in inline styles                               | 📋 |    |    |\n\n### Components\n\n| Name                                                                       | Description                                                                                                                          | 💼 | 🔧 | 💡 |\n| :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |\n| [no-attrs-in-components](docs/rules/no-attrs-in-components.md)             | disallow usage of `this.attrs` in components                                                                                         | ✅  |    |    |\n| [no-attrs-snapshot](docs/rules/no-attrs-snapshot.md)                       | disallow use of attrs snapshot in the `didReceiveAttrs` and `didUpdateAttrs` component hooks                                         | ✅  |    |    |\n| [no-builtin-form-components](docs/rules/no-builtin-form-components.md)     | disallow usage of built-in form components                                                                                           |    |    |    |\n| [no-classic-components](docs/rules/no-classic-components.md)               | enforce using Glimmer components                                                                                                     | ✅  |    |    |\n| [no-component-lifecycle-hooks](docs/rules/no-component-lifecycle-hooks.md) | disallow usage of \"classic\" ember component lifecycle hooks. Render modifiers or custom functional modifiers should be used instead. | ✅  |    |    |\n| [no-on-calls-in-components](docs/rules/no-on-calls-in-components.md)       | disallow usage of `on` to call lifecycle hooks in components                                                                         | ✅  |    |    |\n| [require-tagless-components](docs/rules/require-tagless-components.md)     | disallow using the wrapper element of a component                                                                                    | ✅  |    |    |\n\n### Computed Properties\n\n| Name                                                                                                                                             | Description                                                                                 | 💼 | 🔧 | 💡 |\n| :----------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :- | :- | :- |\n| [computed-property-getters](docs/rules/computed-property-getters.md)                                                                             | enforce the consistent use of getters in computed properties                                |    |    |    |\n| [no-arrow-function-computed-properties](docs/rules/no-arrow-function-computed-properties.md)                                                     | disallow arrow functions in computed properties                                             | ✅  |    |    |\n| [no-assignment-of-untracked-properties-used-in-tracking-contexts](docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md) | disallow assignment of untracked properties that are used as computed property dependencies | ✅  | 🔧 |    |\n| [no-computed-properties-in-native-classes](docs/rules/no-computed-properties-in-native-classes.md)                                               | disallow using computed properties in native classes                                        | ✅  |    |    |\n| [no-deeply-nested-dependent-keys-with-each](docs/rules/no-deeply-nested-dependent-keys-with-each.md)                                             | disallow usage of deeply-nested computed property dependent keys with `@each`               | ✅  |    |    |\n| [no-duplicate-dependent-keys](docs/rules/no-duplicate-dependent-keys.md)                                                                         | disallow repeating computed property dependent keys                                         | ✅  | 🔧 |    |\n| [no-incorrect-computed-macros](docs/rules/no-incorrect-computed-macros.md)                                                                       | disallow incorrect usage of computed property macros                                        | ✅  | 🔧 |    |\n| [no-invalid-dependent-keys](docs/rules/no-invalid-dependent-keys.md)                                                                             | disallow invalid dependent keys in computed properties                                      | ✅  | 🔧 |    |\n| [no-side-effects](docs/rules/no-side-effects.md)                                                                                                 | disallow unexpected side effects in computed properties                                     | ✅  |    |    |\n| [no-volatile-computed-properties](docs/rules/no-volatile-computed-properties.md)                                                                 | disallow volatile computed properties                                                       | ✅  |    |    |\n| [require-computed-macros](docs/rules/require-computed-macros.md)                                                                                 | require using computed property macros when possible                                        | ✅  | 🔧 |    |\n| [require-computed-property-dependencies](docs/rules/require-computed-property-dependencies.md)                                                   | require dependencies to be declared statically in computed properties                       | ✅  | 🔧 |    |\n| [require-return-from-computed](docs/rules/require-return-from-computed.md)                                                                       | disallow missing return statements in computed properties                                   | ✅  |    |    |\n| [use-brace-expansion](docs/rules/use-brace-expansion.md)                                                                                         | enforce usage of brace expansion in computed property dependent keys                        | ✅  |    |    |\n\n### Controllers\n\n| Name                                                                               | Description                           | 💼 | 🔧 | 💡 |\n| :--------------------------------------------------------------------------------- | :------------------------------------ | :- | :- | :- |\n| [alias-model-in-controller](docs/rules/alias-model-in-controller.md)               | enforce aliasing model in controllers |    |    |    |\n| [avoid-using-needs-in-controllers](docs/rules/avoid-using-needs-in-controllers.md) | disallow using `needs` in controllers | ✅  |    |    |\n| [no-controllers](docs/rules/no-controllers.md)                                     | disallow non-essential controllers    |    |    |    |\n\n### Deprecations\n\n| Name                                                                                             | Description                                               | 💼 | 🔧 | 💡 |\n| :----------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- | :- | :- |\n| [closure-actions](docs/rules/closure-actions.md)                                                 | enforce usage of closure actions                          | ✅  |    |    |\n| [new-module-imports](docs/rules/new-module-imports.md)                                           | enforce using \"New Module Imports\" from Ember RFC #176    | ✅  |    |    |\n| [no-array-prototype-extensions](docs/rules/no-array-prototype-extensions.md)                     | disallow usage of Ember's `Array` prototype extensions    |    | 🔧 |    |\n| [no-at-ember-render-modifiers](docs/rules/no-at-ember-render-modifiers.md)                       | disallow importing from @ember/render-modifiers           | ✅  |    |    |\n| [no-deprecated-router-transition-methods](docs/rules/no-deprecated-router-transition-methods.md) | enforce usage of router service transition methods        | ✅  | 🔧 |    |\n| [no-function-prototype-extensions](docs/rules/no-function-prototype-extensions.md)               | disallow usage of Ember's `function` prototype extensions | ✅  |    |    |\n| [no-implicit-injections](docs/rules/no-implicit-injections.md)                                   | enforce usage of implicit service injections              | ✅  | 🔧 |    |\n| [no-mixins](docs/rules/no-mixins.md)                                                             | disallow the usage of mixins                              | ✅  |    |    |\n| [no-new-mixins](docs/rules/no-new-mixins.md)                                                     | disallow the creation of new mixins                       | ✅  |    |    |\n| [no-observers](docs/rules/no-observers.md)                                                       | disallow usage of observers                               | ✅  |    |    |\n| [no-old-shims](docs/rules/no-old-shims.md)                                                       | disallow usage of old shims for modules                   | ✅  | 🔧 |    |\n| [no-string-prototype-extensions](docs/rules/no-string-prototype-extensions.md)                   | disallow usage of `String` prototype extensions           | ✅  |    |    |\n| [template-deprecated-inline-view-helper](docs/rules/template-deprecated-inline-view-helper.md)   | disallow inline {{view}} helper                           | 📋 | 🔧 |    |\n| [template-deprecated-render-helper](docs/rules/template-deprecated-render-helper.md)             | disallow {{render}} helper                                | 📋 | 🔧 |    |\n| [template-no-action](docs/rules/template-no-action.md)                                           | disallow {{action}} helper                                | 📋 |    |    |\n| [template-no-attrs-in-components](docs/rules/template-no-attrs-in-components.md)                 | disallow attrs in component templates                     | 📋 |    |    |\n| [template-no-link-to-positional-params](docs/rules/template-no-link-to-positional-params.md)     | disallow positional params in LinkTo component            | 📋 |    |    |\n| [template-no-link-to-tagname](docs/rules/template-no-link-to-tagname.md)                         | disallow tagName attribute on LinkTo component            | 📋 |    |    |\n| [template-no-route-action](docs/rules/template-no-route-action.md)                               | disallow usage of route-action helper                     | 📋 |    |    |\n| [template-no-unbound](docs/rules/template-no-unbound.md)                                         | disallow {{unbound}} helper                               | 📋 |    |    |\n| [template-no-with](docs/rules/template-no-with.md)                                               | disallow {{with}} helper                                  | 📋 |    |    |\n\n### Ember Data\n\n| Name                                                                                   | Description                                                           | 💼 | 🔧 | 💡 |\n| :------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- | :- | :- | :- |\n| [no-empty-attrs](docs/rules/no-empty-attrs.md)                                         | disallow usage of empty attributes in Ember Data models               |    |    |    |\n| [require-async-inverse-relationship](docs/rules/require-async-inverse-relationship.md) | require inverse to be specified in @belongsTo and @hasMany decorators |    |    |    |\n| [use-ember-data-rfc-395-imports](docs/rules/use-ember-data-rfc-395-imports.md)         | enforce usage of `@ember-data/` package imports instead `ember-data`  | ✅  | 🔧 |    |\n\n### Ember Object\n\n| Name                                                                                       | Description                                                                            | 💼 | 🔧 | 💡 |\n| :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :- | :- | :- |\n| [avoid-leaking-state-in-ember-objects](docs/rules/avoid-leaking-state-in-ember-objects.md) | disallow state leakage                                                                 | ✅  |    |    |\n| [no-get](docs/rules/no-get.md)                                                             | require using ES5 getters instead of Ember's `get` / `getProperties` functions         | ✅  | 🔧 |    |\n| [no-get-with-default](docs/rules/no-get-with-default.md)                                   | disallow usage of the Ember's `getWithDefault` function                                | ✅  | 🔧 |    |\n| [no-modifier-argument-destructuring](docs/rules/no-modifier-argument-destructuring.md)     | disallow destructuring of modifier arguments to avoid consuming tracked data too early |    |    |    |\n| [no-proxies](docs/rules/no-proxies.md)                                                     | disallow using array or object proxies                                                 |    |    |    |\n| [no-try-invoke](docs/rules/no-try-invoke.md)                                               | disallow usage of the Ember's `tryInvoke` util                                         | ✅  |    |    |\n| [require-super-in-lifecycle-hooks](docs/rules/require-super-in-lifecycle-hooks.md)         | require super to be called in lifecycle hooks                                          | ✅  | 🔧 |    |\n| [use-ember-get-and-set](docs/rules/use-ember-get-and-set.md)                               | enforce usage of `Ember.get` and `Ember.set`                                           |    | 🔧 |    |\n\n### Ember Octane\n\n| Name                                                                                       | Description                                                                                                    | 💼                                                              | 🔧 | 💡 |\n| :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :- | :- |\n| [classic-decorator-hooks](docs/rules/classic-decorator-hooks.md)                           | enforce using correct hooks for both classic and non-classic classes                                           | ✅                                                               |    |    |\n| [classic-decorator-no-classic-methods](docs/rules/classic-decorator-no-classic-methods.md) | disallow usage of classic APIs such as `get`/`set` in classes that aren't explicitly decorated with `@classic` | ✅                                                               |    |    |\n| [no-actions-hash](docs/rules/no-actions-hash.md)                                           | disallow the actions hash in components, controllers, and routes                                               | ✅                                                               |    |    |\n| [no-classic-classes](docs/rules/no-classic-classes.md)                                     | disallow \"classic\" classes in favor of native JS classes                                                       | ✅                                                               |    |    |\n| [no-ember-super-in-es-classes](docs/rules/no-ember-super-in-es-classes.md)                 | disallow use of `this._super` in ES class methods                                                              | ✅                                                               | 🔧 |    |\n| [no-empty-glimmer-component-classes](docs/rules/no-empty-glimmer-component-classes.md)     | disallow empty backing classes for Glimmer components                                                          | ✅                                                               |    |    |\n| [no-tracked-built-ins](docs/rules/no-tracked-built-ins.md)                                 | enforce usage of `@ember/reactive/collections` imports instead of `tracked-built-ins`                          |                                                                 | 🔧 |    |\n| [no-tracked-properties-from-args](docs/rules/no-tracked-properties-from-args.md)           | disallow creating @tracked properties from this.args                                                           | ✅                                                               |    |    |\n| [template-no-deprecated](docs/rules/template-no-deprecated.md)                             | disallow using deprecated Glimmer components, helpers, and modifiers in templates                              |                                                                 |    |    |\n| [template-no-let-reference](docs/rules/template-no-let-reference.md)                       | disallow referencing let variables in \\<template\\>                                                             | ![gjs logo](/docs/svgs/gjs.svg) ![gts logo](/docs/svgs/gts.svg) |    |    |\n\n### jQuery\n\n| Name                                               | Description                                        | 💼 | 🔧 | 💡 |\n| :------------------------------------------------- | :------------------------------------------------- | :- | :- | :- |\n| [jquery-ember-run](docs/rules/jquery-ember-run.md) | disallow usage of jQuery without an Ember run loop | ✅  |    |    |\n| [no-global-jquery](docs/rules/no-global-jquery.md) | disallow usage of global jQuery object             | ✅  |    |    |\n| [no-jquery](docs/rules/no-jquery.md)               | disallow any usage of jQuery                       | ✅  |    |    |\n\n### Miscellaneous\n\n| Name                                                                                                                   | Description                                                                                                                   | 💼 | 🔧 | 💡 |\n| :--------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |\n| [named-functions-in-promises](docs/rules/named-functions-in-promises.md)                                               | enforce usage of named functions in promises                                                                                  |    |    |    |\n| [no-html-safe](docs/rules/no-html-safe.md)                                                                             | disallow the use of `htmlSafe`                                                                                                |    |    |    |\n| [no-incorrect-calls-with-inline-anonymous-functions](docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md) | disallow inline anonymous functions as arguments to `debounce`, `once`, and `scheduleOnce`                                    | ✅  |    |    |\n| [no-invalid-debug-function-arguments](docs/rules/no-invalid-debug-function-arguments.md)                               | disallow usages of Ember's `assert()` / `warn()` / `deprecate()` functions that have the arguments passed in the wrong order. | ✅  |    |    |\n| [no-restricted-property-modifications](docs/rules/no-restricted-property-modifications.md)                             | disallow modifying the specified properties                                                                                   |    | 🔧 |    |\n| [no-runloop](docs/rules/no-runloop.md)                                                                                 | disallow usage of `@ember/runloop` functions                                                                                  | ✅  |    |    |\n| [require-fetch-import](docs/rules/require-fetch-import.md)                                                             | enforce explicit import for `fetch()`                                                                                         |    |    |    |\n\n### Possible Errors\n\n| Name                                                                                                                       | Description                                                       | 💼 | 🔧 | 💡 |\n| :------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | :- | :- | :- |\n| [template-no-extra-mut-helper-argument](docs/rules/template-no-extra-mut-helper-argument.md)                               | disallow passing more than one argument to the mut helper         | 📋 |    |    |\n| [template-no-jsx-attributes](docs/rules/template-no-jsx-attributes.md)                                                     | disallow JSX-style camelCase attributes                           |    | 🔧 |    |\n| [template-no-scope-outside-table-headings](docs/rules/template-no-scope-outside-table-headings.md)                         | disallow scope attribute outside th elements                      | 📋 |    |    |\n| [template-no-shadowed-elements](docs/rules/template-no-shadowed-elements.md)                                               | disallow ambiguity with block param names shadowing HTML elements | 📋 |    |    |\n| [template-no-unbalanced-curlies](docs/rules/template-no-unbalanced-curlies.md)                                             | disallow unbalanced mustache curlies                              | 📋 |    |    |\n| [template-no-unknown-arguments-for-builtin-components](docs/rules/template-no-unknown-arguments-for-builtin-components.md) | disallow unknown arguments for built-in components                | 📋 | 🔧 |    |\n\n### Routes\n\n| Name                                                                               | Description                                                                              | 💼 | 🔧 | 💡 |\n| :--------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | :- | :- | :- |\n| [no-capital-letters-in-routes](docs/rules/no-capital-letters-in-routes.md)         | disallow routes with uppercased letters in router.js                                     | ✅  |    |    |\n| [no-controller-access-in-routes](docs/rules/no-controller-access-in-routes.md)     | disallow routes from accessing the controller outside of setupController/resetController | ✅  |    |    |\n| [no-private-routing-service](docs/rules/no-private-routing-service.md)             | disallow injecting the private routing service                                           | ✅  |    |    |\n| [no-shadow-route-definition](docs/rules/no-shadow-route-definition.md)             | enforce no route path definition shadowing                                               | ✅  |    |    |\n| [no-unnecessary-index-route](docs/rules/no-unnecessary-index-route.md)             | disallow unnecessary `index` route definition                                            |    |    |    |\n| [no-unnecessary-route-path-option](docs/rules/no-unnecessary-route-path-option.md) | disallow unnecessary usage of the route `path` option                                    | ✅  | 🔧 |    |\n| [route-path-style](docs/rules/route-path-style.md)                                 | enforce usage of kebab-case (instead of snake_case or camelCase) in route paths          |    |    | 💡 |\n| [routes-segments-snake-case](docs/rules/routes-segments-snake-case.md)             | enforce usage of snake_cased dynamic segments in routes                                  | ✅  |    |    |\n\n### Security\n\n| Name                                                                   | Description                                                     | 💼 | 🔧 | 💡 |\n| :--------------------------------------------------------------------- | :-------------------------------------------------------------- | :- | :- | :- |\n| [template-link-rel-noopener](docs/rules/template-link-rel-noopener.md) | require rel=\"noopener noreferrer\" on links with target=\"_blank\" | 📋 | 🔧 |    |\n| [template-no-triple-curlies](docs/rules/template-no-triple-curlies.md) | disallow usage of triple curly brackets (unescaped variables)   | 📋 |    |    |\n\n### Services\n\n| Name                                                                                                 | Description                                                       | 💼 | 🔧 | 💡 |\n| :--------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------- | :- | :- | :- |\n| [no-implicit-service-injection-argument](docs/rules/no-implicit-service-injection-argument.md)       | disallow omitting the injected service name argument              |    | 🔧 |    |\n| [no-restricted-service-injections](docs/rules/no-restricted-service-injections.md)                   | disallow injecting certain services under certain paths           |    |    |    |\n| [no-unnecessary-service-injection-argument](docs/rules/no-unnecessary-service-injection-argument.md) | disallow unnecessary argument when injecting services             |    | 🔧 |    |\n| [no-unused-services](docs/rules/no-unused-services.md)                                               | disallow unused service injections (see rule doc for limitations) |    |    | 💡 |\n\n### Style\n\n| Name                                                                                         | Description                                                | 💼 | 🔧 | 💡 |\n| :------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :- | :- | :- |\n| [template-no-quoteless-attributes](docs/rules/template-no-quoteless-attributes.md)           | require quotes on all attribute values                     | 📋 | 🔧 |    |\n| [template-no-unnecessary-curly-strings](docs/rules/template-no-unnecessary-curly-strings.md) | disallow unnecessary curly braces in string interpolations | 📋 | 🔧 |    |\n\n### Stylistic Issues\n\n| Name                                                                           | Description                                                                    | 💼 | 🔧 | 💡 |\n| :----------------------------------------------------------------------------- | :----------------------------------------------------------------------------- | :- | :- | :- |\n| [order-in-components](docs/rules/order-in-components.md)                       | enforce proper order of properties in components                               |    | 🔧 |    |\n| [order-in-controllers](docs/rules/order-in-controllers.md)                     | enforce proper order of properties in controllers                              |    | 🔧 |    |\n| [order-in-models](docs/rules/order-in-models.md)                               | enforce proper order of properties in models                                   |    | 🔧 |    |\n| [order-in-routes](docs/rules/order-in-routes.md)                               | enforce proper order of properties in routes                                   |    | 🔧 |    |\n| [template-attribute-indentation](docs/rules/template-attribute-indentation.md) | enforce proper indentation of attributes and arguments in multi-line templates |    |    |    |\n| [template-attribute-order](docs/rules/template-attribute-order.md)             | enforce consistent ordering of attributes in template elements                 |    | 🔧 |    |\n| [template-block-indentation](docs/rules/template-block-indentation.md)         | enforce consistent indentation for block statements and their children         |    | 🔧 |    |\n| [template-eol-last](docs/rules/template-eol-last.md)                           | require or disallow newline at the end of template files                       |    | 🔧 |    |\n| [template-linebreak-style](docs/rules/template-linebreak-style.md)             | enforce consistent linebreaks in templates                                     |    | 🔧 |    |\n| [template-modifier-name-case](docs/rules/template-modifier-name-case.md)       | require dasherized names for modifiers                                         |    | 🔧 |    |\n| [template-no-only-default-slot](docs/rules/template-no-only-default-slot.md)   | disallow using only the default slot                                           |    | 🔧 |    |\n| [template-quotes](docs/rules/template-quotes.md)                               | enforce consistent quote style in templates                                    |    | 🔧 |    |\n| [template-template-length](docs/rules/template-template-length.md)             | enforce template size constraints                                              |    |    |    |\n\n### Testing\n\n| Name                                                                                                   | Description                                                                                              | 💼 | 🔧 | 💡 |\n| :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | :- | :- | :- |\n| [no-current-route-name](docs/rules/no-current-route-name.md)                                           | disallow usage of the `currentRouteName()` test helper                                                   |    |    |    |\n| [no-ember-testing-in-module-scope](docs/rules/no-ember-testing-in-module-scope.md)                     | disallow use of `Ember.testing` in module scope                                                          | ✅  |    |    |\n| [no-invalid-test-waiters](docs/rules/no-invalid-test-waiters.md)                                       | disallow incorrect usage of test waiter APIs                                                             | ✅  |    |    |\n| [no-legacy-test-waiters](docs/rules/no-legacy-test-waiters.md)                                         | disallow the use of the legacy test waiter APIs                                                          | ✅  |    |    |\n| [no-noop-setup-on-error-in-before](docs/rules/no-noop-setup-on-error-in-before.md)                     | disallows using no-op setupOnerror in `before` or `beforeEach`                                           | ✅  | 🔧 |    |\n| [no-pause-test](docs/rules/no-pause-test.md)                                                           | disallow usage of the `pauseTest` helper in tests                                                        | ✅  |    |    |\n| [no-replace-test-comments](docs/rules/no-replace-test-comments.md)                                     | disallow 'Replace this with your real tests' comments in test files                                      |    |    |    |\n| [no-restricted-resolver-tests](docs/rules/no-restricted-resolver-tests.md)                             | disallow the use of patterns that use the restricted resolver in tests                                   | ✅  |    |    |\n| [no-settled-after-test-helper](docs/rules/no-settled-after-test-helper.md)                             | disallow usage of `await settled()` right after test helper that calls it internally                     | ✅  | 🔧 |    |\n| [no-test-and-then](docs/rules/no-test-and-then.md)                                                     | disallow usage of the `andThen` test wait helper                                                         | ✅  |    |    |\n| [no-test-import-export](docs/rules/no-test-import-export.md)                                           | disallow importing of \"-test.js\" in a test file and exporting from a test file                           | ✅  |    |    |\n| [no-test-module-for](docs/rules/no-test-module-for.md)                                                 | disallow usage of `moduleFor`, `moduleForComponent`, etc                                                 | ✅  |    |    |\n| [no-test-support-import](docs/rules/no-test-support-import.md)                                         | disallow importing of \"test-support\" files in production code.                                           | ✅  |    |    |\n| [no-test-this-render](docs/rules/no-test-this-render.md)                                               | disallow usage of the `this.render` in tests, recommending to use @ember/test-helpers' `render` instead. | ✅  |    |    |\n| [prefer-ember-test-helpers](docs/rules/prefer-ember-test-helpers.md)                                   | enforce usage of `@ember/test-helpers` methods over native window methods                                | ✅  |    |    |\n| [require-valid-css-selector-in-test-helpers](docs/rules/require-valid-css-selector-in-test-helpers.md) | disallow using invalid CSS selectors in test helpers                                                     | ✅  | 🔧 |    |\n\n<!-- end auto-generated rules list -->\n\n## 🍻 Contribution Guide\n\nIf you have any suggestions, ideas, or problems, feel free to [create an issue](https://github.com/ember-cli/eslint-plugin-ember/issues/new), but first please make sure your question does not repeat [previous ones](https://github.com/ember-cli/eslint-plugin-ember/issues).\n\n### Creating a New Rule\n\n- [Create an issue](https://github.com/ember-cli/eslint-plugin-ember/issues/new) with a description of the proposed rule\n- Create files for the [new rule](https://eslint.org/docs/developer-guide/working-with-rules):\n  - `lib/rules/new-rule.js` (implementation, see [no-proxies](lib/rules/no-proxies.js) for an example)\n  - `docs/rules/new-rule.md` (documentation, start from the template -- [raw](https://raw.githubusercontent.com/ember-cli/eslint-plugin-ember/master/docs/rules/_TEMPLATE_.md), [rendered](docs/rules/_TEMPLATE_.md))\n  - `tests/lib/rules/new-rule.js` (tests, see [no-proxies](tests/lib/rules/no-proxies.js) for an example)\n- Run `pnpm update` to automatically update the README and other files (and re-run this if you change the rule name or description)\n- Make sure your changes will pass [CI](./.github/workflows/ci.yml) by running:\n  - `pnpm test`\n  - `pnpm lint` (`pnpm lint:js --fix` can fix many errors)\n- Create a PR and link the created issue in the description\n\nNote that new rules should not immediately be added to the [recommended](./lib/recommended-rules.js) configuration, as we only consider such breaking changes during major version updates.\n\n## 🔓 License\n\nSee the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Release Process\n\nReleases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged.\n\n## Preparation\n\nSince the majority of the actual release process is automated, the remaining tasks before releasing are:\n\n- correctly labeling **all** pull requests that have been merged since the last release\n- updating pull request titles so they make sense to our users\n\nSome great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall\nguiding principle here is that changelogs are for humans, not machines.\n\nWhen reviewing merged PR's the labels to be used are:\n\n- breaking - Used when the PR is considered a breaking change.\n- enhancement - Used when the PR adds a new feature or enhancement.\n- bug - Used when the PR fixes a bug included in a previous release.\n- documentation - Used when the PR adds or updates documentation.\n- internal - Internal changes or things that don't fit in any other category.\n\n**Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal`\n\n## Release\n\nOnce the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/ember-cli/eslint-plugin-ember/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR\n"
  },
  {
    "path": "docs/rules/_TEMPLATE_.md",
    "content": "# TODO: rule-name-goes-here\n\n(TODO: only include this line if the rule is recommended) ✅ The `\"extends\": \"plugin:ember/recommended\"` property in a configuration file enables this rule.\n\n(TODO: only include this line if the rule is fixable) 🔧 The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.\n\nTODO: context about the problem goes here\n\n## Rule Details\n\nTODO: what the rule does goes here\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// TODO: Example 1\n```\n\n```js\n// TODO: Example 2\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// TODO: Example 1\n```\n\n```js\n// TODO: Example 2\n```\n\n## Migration\n\nTODO: suggest any fast/automated techniques for fixing violations in a large codebase\n\n- TODO: suggestion on how to fix violations using find-and-replace / regexp\n- TODO: suggestion on how to fix violations using a codemod\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\nTODO: exclude this section if the rule has no extra configuration.\nTODO: ensure `meta.schema` contains descriptions, constraints, defaults, etc for all options.\n\n<!-- end auto-generated rule options list -->\n\n## Related Rules\n\n- [TODO-related-rule-name1](related-rule-name1.md)\n- [TODO-related-rule-name2](related-rule-name2.md)\n\n## References\n\n- TODO: link to relevant documentation goes here)\n- TODO: link to relevant function spec goes here\n- TODO: link to relevant guide goes here\n"
  },
  {
    "path": "docs/rules/alias-model-in-controller.md",
    "content": "# ember/alias-model-in-controller\n\n<!-- end auto-generated rule header -->\n\nIt makes code more readable if the model has the same name as a subject.\n\n## Examples\n\nWe can do this in two ways:\n\n- Alias the model to another property name in the Controller:\n\n  ```js\n  import Controller from '@ember/controller';\n  import { alias } from '@ember/object/computed';\n\n  export default Controller.extend({\n    nail: alias('model'),\n  });\n  ```\n\n- Set it as a property in the Route's `setupController` method:\n\n  ```js\n  import Route from '@ember/routing/route';\n\n  export default Route.extend({\n    setupController(controller, model) {\n      controller.set('nail', model);\n    },\n  });\n  ```\n\nIf you're passing [multiple models](https://guides.emberjs.com/v2.13.0/routing/specifying-a-routes-model/#toc_multiple-models) as an [`RSVP.hash`](https://emberjs.com/api/classes/RSVP.html#method_hash), you can also alias nested properties:\n\n```js\nimport Controller from '@ember/controller';\nimport { reads } from '@ember/object/computed';\n\nexport default Controller.extend({\n  people: reads('model.people'),\n  pets: reads('model.pets'),\n});\n```\n\n## Help Wanted\n\n| Issue                                      | Link                                                                |\n| :----------------------------------------- | :------------------------------------------------------------------ |\n| ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) |\n"
  },
  {
    "path": "docs/rules/avoid-leaking-state-in-ember-objects.md",
    "content": "# ember/avoid-leaking-state-in-ember-objects\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDon't use arrays and objects as default properties in classic classes or mixins. In native classes, it is safe to assign objects to fields.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Foo.extend({\n  items: [],\n\n  actions: {\n    addItem(item) {\n      this.items.pushObject(item);\n    },\n  },\n});\n```\n\n```js\nimport Mixin from '@ember/object/mixin';\n\nexport default Mixin.create({\n  items: [],\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Foo.extend({\n  init(...args) {\n    this._super(...args);\n\n    this.items = this.items || [];\n  },\n\n  actions: {\n    addItem(item) {\n      this.items.pushObject(item);\n    },\n  },\n});\n```\n\n```js\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\n\nexport default class FooComponent extends Component {\n  items = [];\n\n  @action\n  addItem(item) {\n    this.items = [...this.items, item];\n  }\n}\n```\n\n## Configuration\n\nIf you have custom properties where you know that the shared state won't be a problem (for example, read-only configuration values), you can configure this rule to ignore them by passing the property names to the rule as follows. Note that this rule will always automatically ignore known-safe Ember properties such as `actions`.\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/avoid-leaking-state-in-ember-objects': [\n      'error',\n      ['array', 'of', 'ignored', 'properties'],\n    ],\n  },\n};\n```\n\n## References\n\n- [Dockyard blog](https://dockyard.com/blog/2015/09/18/ember-best-practices-avoid-leaking-state-into-factories)\n- [Ember native classes guides](https://guides.emberjs.com/release/upgrading/current-edition/native-classes/)\n- [ember-native-class-codemod](https://github.com/ember-codemods/ember-native-class-codemod)\n"
  },
  {
    "path": "docs/rules/avoid-using-needs-in-controllers.md",
    "content": "# ember/avoid-using-needs-in-controllers\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nAvoid using `needs` to load other controllers. Inject the required controller instead. `needs` was deprecated in ember 1.x and removed in 2.0.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Controller.extend({\n  needs: ['comments'],\n  newComments: alias('controllers.comments.newest'),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Controller, { inject as controller } from '@ember/controller';\n\nexport default Component.extend({\n  comments: controller(),\n  newComments: alias('comments.newest'),\n});\n```\n\n## Help Wanted\n\n| Issue                                      | Link                                                                |\n| :----------------------------------------- | :------------------------------------------------------------------ |\n| ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) |\n"
  },
  {
    "path": "docs/rules/classic-decorator-hooks.md",
    "content": "# ember/classic-decorator-hooks\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUse the correct lifecycle hooks in classic and non-classic classes. Classic\nclasses should use `init`, and non-classic classes should use `constructor`.\nAdditionally, non-classic classes may not use `destroy`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default class MyService extends Service {\n  init() {\n    // ...\n  }\n\n  destroy() {\n    // ...\n  }\n}\n```\n\n```js\n@classic\nexport default class MyService extends Service {\n  constructor(...args) {\n    super(...args);\n    // ...\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n@classic\nexport default class MyService extends Service {\n  init() {\n    // ...\n  }\n\n  destroy() {\n    // ...\n  }\n}\n```\n\n```js\nexport default class MyService extends Service {\n  constructor(...args) {\n    super(...args);\n    // ...\n  }\n\n  willDestroy() {\n    // ...\n  }\n}\n```\n\n## References\n\n- [ember-classic-decorator](https://github.com/pzuraq/ember-classic-decorator)\n\n## Related Rules\n\n- [classic-decorator-no-classic-methods](classic-decorator-no-classic-methods.md)\n"
  },
  {
    "path": "docs/rules/classic-decorator-no-classic-methods.md",
    "content": "# ember/classic-decorator-no-classic-methods\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows the use of the following classic API methods within a class:\n\n- `get`\n- `set`\n- `getProperties`\n- `setProperties`\n- `getWithDefault`\n- `incrementProperty`\n- `decrementProperty`\n- `toggleProperty`\n- `addObserver`\n- `removeObserver`\n- `notifyPropertyChange`\n- `cacheFor`\n- `proto`\n\nThese are \"classic\" API methods, and their usage is discouraged in Octane.\nNon-method versions of them can still be used, e.g. `@ember/object#get` and\n`@ember/object#set` instead of `this.get` and `this.set`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default class MyService extends Service {\n  constructor(...args) {\n    super(...args);\n    this.set('foo', 'bar');\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n@classic\nexport default class MyService extends Service {\n  constructor(...args) {\n    super(...args);\n    this.set('foo', 'bar');\n  }\n}\n```\n\n```js\nimport { set } from '@ember/object';\n\nexport default class MyService extends Service {\n  constructor(...args) {\n    super(...args);\n    set(this, 'foo', 'bar');\n  }\n}\n```\n\n```js\nimport { tracked } from '@glimmer/tracking';\n\nexport default class MyService extends Service {\n  @tracked foo = 'bar';\n}\n```\n\n## References\n\n- [ember-classic-decorator](https://github.com/pzuraq/ember-classic-decorator)\n\n## Related Rules\n\n- [no-get](no-get.md)\n- [classic-decorator-hooks](classic-decorator-hooks.md)\n- [Tracked properties](https://guides.emberjs.com/release/upgrading/current-edition/tracked-properties/)\n"
  },
  {
    "path": "docs/rules/closure-actions.md",
    "content": "# ember/closure-actions\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nAlways use closure actions (according to DDAU convention). Exception: only when you need bubbling.\n\n## Examples\n\n```js\nexport default Controller.extend({\n  actions: {\n    detonate() {\n      alert('Kabooom');\n    },\n  },\n});\n```\n\nExamples of **incorrect** code for this rule:\n\n```hbs\n{{awful-component detonate='detonate'}}\n```\n\n```js\n// awful-component.js\nexport default Component.extend({\n  actions: {\n    pushLever() {\n      this.sendAction('detonate');\n    },\n  },\n});\n```\n\nExamples of **correct** code for this rule:\n\n```hbs\n{{pretty-component boom=(action 'detonate')}}\n```\n\n```js\n// pretty-component.js\nexport default Component.extend({\n  actions: {\n    pushLever() {\n      this.boom();\n    },\n  },\n});\n```\n\n## References\n\n- [RFC](https://github.com/emberjs/rfcs/blob/master/text/0335-deprecate-send-action.md) to deprecate `sendAction`\n"
  },
  {
    "path": "docs/rules/computed-property-getters.md",
    "content": "# ember/computed-property-getters\n\n<!-- end auto-generated rule header -->\n\nEnforce the consistent use of getters in computed properties.\n\nComputed properties may be created with or without a `get` method. This rule ensures that the choice\nis consistent.\n\n## Configuration\n\nThis rule takes a single string option.\n\nString option:\n\n- `\"always-with-setter\"` (default) getters are _required_ when computed property has a setter\n- `\"always\"` getters are _required_ in computed properties\n- `\"never\"` getters are _never allowed_ in computed properties\n\n## Examples\n\n### always-with-setter\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\n// BAD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', {\n    get() {\n      // ...\n    },\n  }),\n});\n\n// GOOD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', function () {\n    // ...\n  }),\n});\n\n// GOOD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', {\n    set() {\n      // ...\n    },\n    get() {\n      // ...\n    },\n  }),\n});\n```\n\n### always\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\n// GOOD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', {\n    get() {\n      // ...\n    },\n  }),\n});\n\n// BAD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', function () {\n    // ...\n  }),\n});\n```\n\n### never\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\n// GOOD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', function () {\n    // ...\n  }),\n});\n\n// BAD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', {\n    get() {\n      // ...\n    },\n  }),\n});\n\n// BAD\nEmberObject.extend({\n  fullName: computed('firstName', 'lastName', {\n    get() {\n      // ...\n    },\n    set() {\n      // ...\n    },\n  }),\n});\n```\n\n## Help Wanted\n\n| Issue                                      | Link                                                                |\n| :----------------------------------------- | :------------------------------------------------------------------ |\n| ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) |\n"
  },
  {
    "path": "docs/rules/jquery-ember-run.md",
    "content": "# ember/jquery-ember-run\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDon’t use jQuery without the Ember Run Loop.\n\nUsing plain jQuery invokes actions outside of the Ember Run Loop. In order to have a control on all operations in Ember, it's good practice to trigger actions in a run loop (using one of the [@ember/runloop](https://api.emberjs.com/ember/3.24/classes/@ember%2Frunloop) functions).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport $ from 'jquery';\n\n$('#something-rendered-by-jquery-plugin').on('click', () => {\n  this._handlerActionFromController();\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport $ from 'jquery';\nimport { bind } from '@ember/runloop';\n\n$('#something-rendered-by-jquery-plugin').on(\n  'click',\n  bind(this, this._handlerActionFromController)\n);\n```\n\n## Related\n\n- [no-global-jquery](./no-global-jquery.md)\n- [no-jquery](./no-jquery.md)\n"
  },
  {
    "path": "docs/rules/named-functions-in-promises.md",
    "content": "# ember/named-functions-in-promises\n\n<!-- end auto-generated rule header -->\n\nUse named functions defined on objects to handle promises.\n\nWhen you use promises and its handlers, use named functions defined on parent object. Thus, you will be able to test them in isolation using unit tests without any additional mocking.\n\n## Examples\n\n```js\nexport default Component.extend({\n  actions: {\n    // BAD\n    updateUserBad(user) {\n      user\n        .save()\n        .then(() => {\n          return user.reload();\n        })\n        .then(() => {\n          this.notifyAboutSuccess();\n        })\n        .catch(() => {\n          this.notifyAboutFailure();\n        });\n    },\n    // GOOD\n    updateUserGood1(user) {\n      user\n        .save()\n        .then(this._reloadUser.bind(this))\n        .then(this._notifyAboutSuccess.bind(this))\n        .catch(this._notifyAboutFailure.bind(this));\n    },\n    // GOOD if allowSimpleArrowFunction: true\n    updateUserGood2(user) {\n      user\n        .save()\n        .then(() => this._reloadUser())\n        .then(() => this._notifyAboutSuccess())\n        .catch(() => this._notifyAboutFailure());\n    },\n  },\n  _reloadUser(user) {\n    return user.reload();\n  },\n  _notifyAboutSuccess() {\n    // ...\n  },\n  _notifyAboutFailure() {\n    // ...\n  },\n});\n```\n\nAnd then you can make simple unit tests for handlers:\n\n```js\ntest('it reloads user in promise handler', function (assert) {\n  const component = this.subject();\n  // assuming that you have `user` defined with kind of sinon spy on its reload method\n  component._reloadUser(user);\n  assert.ok(userReloadSpy.calledOnce, 'user#reload should be called once');\n});\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                       | Description                                                                                                                                                                                                 | Type    | Default |\n| :------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `allowSimpleArrowFunction` | Enabling allows arrow function expressions that do not have block bodies. These simple arrow functions must also only contain a single function call. For example: `.then(user => this._reloadUser(user))`. | Boolean | `false` |\n\n<!-- end auto-generated rule options list -->\n"
  },
  {
    "path": "docs/rules/new-module-imports.md",
    "content": "# ember/new-module-imports\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUse \"New Module Imports\" from Ember RFC #176.\n\n[RFC #176](https://github.com/emberjs/rfcs/pull/176) introduced as new public\nAPI for Ember.js based on ES6 module imports.\n\nIf you use `ember-cli-babel` with version `6.6.0` or above you can start using\nthe \"New Module Imports\" instead of the `Ember` global directly. This will\nenable us to build better tree shaking feature into Ember CLI.\n\nIf you want to transition to new module imports in old Ember app use dedicated [codemod](https://github.com/ember-cli/ember-modules-codemod). For more information, please read [the following article](https://medium.com/@Dhaulagiri/embers-javascript-modules-api-b4483782f329) by Brian Runnells.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nEmber.Component.extend({});\nEmber.Object.extend({});\nEmber.computed(function () {});\nEmber.inject.service('foo');\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport EmberObject, { computed } from '@ember/object';\nimport Service, { inject } from '@ember/service';\n```\n"
  },
  {
    "path": "docs/rules/no-actions-hash.md",
    "content": "# ember/no-actions-hash\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows the actions hash in components and controllers.\n\nEmber Octane includes a rethink of event handling in Ember. The `actions` hash and `{{action}}` modifier and helper are no longer needed. To provide the correct context to functions (binding), you should now use the `@action` decorator. In templates, the `{{on}}` modifier can be used to set up event handlers and the `{{fn}}` helper can be used for partial application.\n\n## Rule Detail\n\nUse the `@action` decorator or `foo: action(function() {}))` syntax instead of an `actions` hash.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// Bad, with classic class\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  actions: {\n    foo() {},\n  },\n});\n```\n\n```js\n// Bad, with native class\nimport Component from '@ember/component';\n\nexport class MyComponent extends Component {\n  actions = {\n    foo() {},\n  };\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// Good, with classic class\nimport Component from '@ember/component';\nimport { action } from '@ember/object';\n\nexport default Component.extend({\n  foo: action(function () {}),\n});\n```\n\n```js\n// Good, with native class\nimport Component from '@ember/component';\nimport { action } from '@ember/object';\n\nexport class MyComponent extends Component {\n  @action\n  foo() {}\n}\n```\n\n## Further Reading\n\n- [`{{on}}` Modifier RFC](https://github.com/emberjs/rfcs/pull/471)\n- [`{{fn}}` Helper RFC](https://github.com/emberjs/rfcs/pull/470)\n- [Ember Octane Update: What's up with `@action`?](https://www.pzuraq.com/ember-octane-update-action/)\n"
  },
  {
    "path": "docs/rules/no-array-prototype-extensions.md",
    "content": "# ember/no-array-prototype-extensions\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nBy default, Ember extends certain native JavaScript objects with additional methods. This can lead to problems in some situations. One example is relying on these methods in an addon that is used inside an app that has the extensions disabled.\n\nThe prototype extensions for the `Array` object were deprecated in [RFC #848](https://rfcs.emberjs.com/id/0848-deprecate-array-prototype-extensions).\n\nSome alternatives:\n\n- Use native array functions instead of `.filterBy()`, `.toArray()` in Ember modules\n- Use lodash helper functions instead of `.uniqBy()`, `.sortBy()` in Ember modules\n- Use immutable update style with `@tracked` properties or `TrackedArray` from `tracked-built-ins` instead of `.pushObject`, `removeObject` in Ember modules\n\n## Rule Details\n\nThis rule will disallow method calls that match any of the forbidden `Array` prototype extension method names.\n\nThe rule autofixes all [EmberArray](https://api.emberjs.com/ember/release/classes/EmberArray) functions. It does not autofix the mutation functions from [MutableArray](https://api.emberjs.com/ember/release/classes/MutableArray) or `firstObject` / `lastObject`, as these involve reactivity/observability and may require a more involved change to convert to `@tracked` or `TrackedArray`.\n\nTo reduce false positives, the rule ignores some common known-non-array classes/objects whose functions overlap with the array extension function names:\n\n- `Set.clear()`\n- `Map.clear()`\n- `localStorage.clear()` / `sessionStorage.clear()`\n- `Promise.any()` / `Promise.reject()`\n- Lodash / jQuery\n- Ember Data `this.store` service\n- etc\n\nIf you run into additional false positives, please file a bug or submit a PR to add it to the rule's hardcoded ignore list.\n\nThis rule is not in the `recommended` configuration because of the risk of false positives.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n/** Helper functions **/\nimport Component from '@glimmer/component';\n\nexport default class SampleComponent extends Component {\n  abc = ['x', 'y', 'z', 'x'];\n\n  def = this.abc.without('x');\n  ghi = this.abc.uniq();\n  jkl = this.abc.toArray();\n  mno = this.abc.uniqBy('y');\n  pqr = this.abc.sortBy('z');\n}\n```\n\n```js\n/** Observable-based functions **/\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\n\nexport default class SampleComponent extends Component {\n  abc = [];\n  @action\n  someAction(newItem) {\n    this.abc.pushObject('1');\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n/** Helper functions **/\nimport Component from '@glimmer/component';\nimport { uniqBy, sortBy } from 'lodash';\n\nexport default class SampleComponent extends Component {\n  abc = ['x', 'y', 'z', 'x'];\n\n  def = this.abc.filter((el) => el !== 'x');\n  ghi = [...new Set(this.abc)];\n  jkl = [...this.abc];\n  mno = uniqBy(this.abc, 'y');\n  pqr = sortBy(this.abc, 'z');\n}\n```\n\n```js\n/** Observable-based functions **/\n/** Use immutable tracked property is OK **/\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\n\nexport default class SampleComponent extends Component {\n  @tracked abc = [];\n\n  @action\n  someAction(newItem) {\n    this.abc = [...this.abc, newItem];\n  }\n}\n```\n\n```js\n/** Observable-based functions **/\n/** Use TrackedArray is OK **/\nimport Component from '@glimmer/component';\nimport { action } from '@ember/object';\nimport { tracked } from '@glimmer/tracking';\nimport { TrackedArray } from 'tracked-built-ins';\n\nexport default class SampleComponent extends Component {\n  @tracked abc = new TrackedArray();\n\n  @action\n  someAction(newItem) {\n    abc.push(newItem);\n  }\n}\n```\n\n```js\n/** Direct usage of `@ember/array` **/\n/** Use A() is OK **/\nimport { A } from '@ember/array';\n\nconst arr = A(['a', 'a', 'b', 'b']);\narr.uniq();\n```\n\n## References\n\n- [EmberArray](https://api.emberjs.com/ember/release/classes/EmberArray)\n- Ember [MutableArray](https://api.emberjs.com/ember/release/classes/MutableArray)\n- [Ember Prototype extensions documentation](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/)\n- [Ember Array prototype extensions deprecation RFC](https://rfcs.emberjs.com/id/0848-deprecate-array-prototype-extensions)\n- [Native JavaScript array functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)\n\n## Related Rules\n\n- [no-array-prototype-extensions](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-array-prototype-extensions.md) from ember-template-lint\n- [no-function-prototype-extensions](no-function-prototype-extensions.md)\n- [no-string-prototype-extensions](no-string-prototype-extensions.md)\n"
  },
  {
    "path": "docs/rules/no-arrow-function-computed-properties.md",
    "content": "# ember/no-arrow-function-computed-properties\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nArrow functions should not be used in computed properties because they are unable to access other properties (using `this.property`) of the same object. Accidental usage can thus lead to bugs.\n\n## Rule Details\n\nThis rule disallows using arrow functions in computed properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\nconst Person = EmberObject.extend({\n  fullName: computed('firstName', 'lastName', () => {\n    return `${this.firstName} ${this.lastName}`;\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\nconst Person = EmberObject.extend({\n  fullName: computed('firstName', 'lastName', function () {\n    return `${this.firstName} ${this.lastName}`;\n  }),\n});\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name               | Description                                                                                                                      | Type    | Default |\n| :----------------- | :------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `onlyThisContexts` | Whether the rule should allow or disallow computed properties where the arrow function body does not contain a `this` reference. | Boolean | `false` |\n\n<!-- end auto-generated rule options list -->\n\n## References\n\n- [Arrow function spec](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)\n- [Computed property spec](https://api.emberjs.com/ember/release/classes/ComputedProperty)\n"
  },
  {
    "path": "docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md",
    "content": "# ember/no-assignment-of-untracked-properties-used-in-tracking-contexts\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEmber 3.13 added an assertion that fires when using assignment `this.x = 123` on an untracked property that is used in a tracking context such as a computed property.\n\n> You attempted to update \"propertyX\" to \"valueY\",\n> but it is being tracked by a tracking context, such as a template, computed property, or observer.\n>\n> In order to make sure the context updates properly, you must invalidate the property when updating it.\n>\n> You can mark the property as `@tracked`, or use `@ember/object#set` to do this.\n\n## Rule Details\n\nThis rule catches assignments of untracked properties that are used as computed property dependency keys.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { computed } from '@ember/object';\nimport Component from '@ember/component';\n\nclass MyComponent extends Component {\n  @computed('x') get myProp() {\n    return this.x;\n  }\n  myFunction() {\n    this.x = 123; // Not okay to use assignment here.\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { computed, set } from '@ember/object';\nimport Component from '@ember/component';\n\nclass MyComponent extends Component {\n  @computed('x') get myProp() {\n    return this.x;\n  }\n  myFunction() {\n    set(this, 'x', 123); // Okay because it uses set.\n  }\n}\n```\n\n```js\nimport { computed, set } from '@ember/object';\nimport Component from '@ember/component';\nimport { tracked } from '@glimmer/tracking';\n\nclass MyComponent extends Component {\n  @tracked x;\n  @computed('x') get myProp() {\n    return this.x;\n  }\n  myFunction() {\n    this.x = 123; // Okay because `x` is a tracked property.\n  }\n}\n```\n\n## Migration\n\nThe autofixer for this rule will update assignments to use `set`. Alternatively, you can begin using tracked properties.\n\n## Configuration\n\n- object -- containing the following properties:\n  - array -- `extraMacros` -- Array of configurations for custom computed property macros which have dependent keys as arguments, each with hte following properties:\n    - string -- `name` -- The name the macro is exported with\n    - string -- `path` -- The file path used for importing the macro\n    - string -- `indexName` -- If this macro can also be imported through an index (like `computed` for `computed.and`), include it here\n    - string -- `indexPath` -- The path for importing the index. For example, with `import { computed } from '@ember/object'` and `computed.and(...)`, `@ember/object` is the `indexPath` and `computed` is the `indexName`.\n    - array -- `argumentFormat` -- array of configurations for how to parse the arguments of the macro to extract the computed dependencies, with at least one of the following properties:\n      - object -- `strings` -- Configuration for extracting raw strings from the argument list, with the following options:\n        - number -- `count` -- How many arguments to consider as dependencies. Use `Number.MAX_VALUE` for all of them.\n        - number -- `startIndex` -- Defaults to zero. If it's something else, that many arguments will be skipped before checking for `count` dependencies.\n      - object -- `objects` -- Configuration for extracting the values of an object as dependency keys, with the following properties:\n        - number -- `index` -- The index of the argument to be checked.\n        - array -- `keys` -- Array of strings for which keys values should be checked for. If not provided, all values will be checked.\n\nExample configuration:\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/no-assignment-of-untracked-properties-used-in-tracking-contexts': {\n      extraMacros: [\n        {\n          name: 'rejectBy',\n          path: 'custom-macros/macros',\n          indexName: 'customComputed',\n          indexPath: 'custom-macros',\n          argumentFormat: [\n            {\n              strings: {\n                count: 1,\n              },\n            },\n          ],\n        },\n        {\n          name: 't',\n          path: 'ember-intl',\n          argumentFormat: [\n            {\n              objects: {\n                index: 1,\n              },\n            },\n          ],\n        },\n      ],\n    },\n  },\n};\n```\n\nThis configuration works for the [t macro](https://ember-intl.github.io/ember-intl/versions/master/docs/guide/translating-text#t) from ember-intl, and a custom `rejectBy` macro that behaves similarly to `filterBy` (with the second string argument not being a dependency):\n\n```js\nimport { A, isArray } from '@ember/array';\nimport { get } from '@ember/object';\n\nexport default function rejectBy(dependentKey, propertyKey, value) {\n  return computed(`${dependentKey}.@each.${propertyKey}`, function () {\n    const parent = get(this, dependentKey);\n    if (!isArray(parent)) {\n      return A();\n    }\n    const callback =\n      arguments.length === 2\n        ? (item) => !get(item, propertyKey)\n        : (item) => get(item, propertyKey) !== value;\n    return A(parent.filter(callback));\n  });\n}\n```\n\n## References\n\n- [Spec](https://api.emberjs.com/ember/release/functions/@ember%2Fobject/set) for `set()`\n- [Spec](https://api.emberjs.com/ember/3.16/functions/@glimmer%2Ftracking/tracked) for `@tracked`\n- [Guide](https://guides.emberjs.com/release/upgrading/current-edition/tracked-properties/) for tracked properties\n"
  },
  {
    "path": "docs/rules/no-at-ember-render-modifiers.md",
    "content": "# ember/no-at-ember-render-modifiers\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nWhat's wrong with `{{did-insert}}`, `{{did-update}}`, and `{{will-destroy}}`?\n\nThese modifiers were meant for temporary migration purposes for quickly migrating `@ember/component` from before Octane to the `@glimmer/component` we have today. Since `@ember/component` implicitly had a wrapping `div` around each component and `@glimmer/component`s have \"outer HTML\" semantics, an automated migration could have ended up looking something like:\n\n```hbs\n<div\n  {{did-insert this.doInsertBehavior}}\n  {{did-update this.doUpdateBehavior @arg1 @arg2}}\n  {{will-destroy this.doDestroyBehavior}}\n>\n  ...\n</div>\n```\n\nIt was intended that this would be a temporary step to help get folks off of `@ember/components` quickly in early 2020 when folks migrated to the Octane editon, but some folks continued using these modifiers.\n\nAdditionally, this style of mananging data flow has some flaws:\n\n- an element is required\n  - this can be mitigated by using helpers, but they have the same problems mentioned below\n- the behavior that is used with these modifiers can cause extra renders and infinite rendering loops\n  - this is the nature of \"effect\"-driven development / data-flow, every time an effect runs, rendering must re-occur.\n- behavior that needs to be co-located is spread out, making maintenance and debugging harder\n  - each part of the responsibility of a \"behavior\" or \"feature\" is spread out, making it harder to find and comprehend the full picture of that behavior or feature.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```js\nimport didInsert from '@ember/render-modifiers/modifiers/did-insert';\n```\n\n```js\nimport didUpdate from '@ember/render-modifiers/modifiers/did-update';\n```\n\n```js\nimport willDestroy from '@ember/render-modifiers/modifiers/will-destroy';\n```\n\nFor more examples, see [the docs on ember-template-lint](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-at-ember-render-modifiers.md).\n\n## References\n\n- [Editions](https://emberjs.com/editions/)\n- [Octane Upgrade Guide](https://guides.emberjs.com/release/upgrading/current-edition/)\n- [Component Documentation](https://guides.emberjs.com/release/components/)\n- [Avoiding Lifecycle in Component](https://nullvoxpopuli.com/avoiding-lifecycle)\n- [The `ember-template-lint` version of this rule](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-at-ember-render-modifiers.md)\n- [`ember-modifier`](https://github.com/ember-modifier/ember-modifier)\n- [`@ember/render-modifiers`](https://github.com/emberjs/ember-render-modifiers) (deprecated)\n"
  },
  {
    "path": "docs/rules/no-attrs-in-components.md",
    "content": "# ember/no-attrs-in-components\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDo not use `this.attrs`.\n\nIn the run-up to Ember 2.0, several blog articles were written about using `this.attrs` but this feature has never been documented as a public API. Typically people use `attrs` to denote properties and methods as having been \"passed\" in to a component and bare names as properties local to the component. This is useful and some iteration of Ember will have this built into the programming model, but for now we should not use `attrs`.\n\nIn JavaScript we typically prefix \"private\" things with `_`. If you want to create this notion in a component, we can leverage this long standing convention. Things that are local are technically private as a component's scope is isolated so marking them with `_` makes sense semantically. Passed items can use the bare name, as they are effectively public/protected methods and properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nconst name = this.attrs.name;\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// Prefix private properties with _\nconst name = this._name;\n```\n\n## Further Reading\n\n- <https://locks.svbtle.com/to-attrs-or-not-to-attrs>\n"
  },
  {
    "path": "docs/rules/no-attrs-snapshot.md",
    "content": "# ember/no-attrs-snapshot\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow use of attrs snapshot in `didReceiveAttrs` and `didUpdateAttrs`.\n\nDo not use the arguments (attrs) that are passed in `didReceiveAttrs` and `didUpdateAttrs`. Using the arguments (attrs) in these hooks can result in performance degradation in your application.\n\n## Rule Details\n\nIn 2.0.0, `didReceiveAttrs` and `didUpdateAttrs` hooks were introduced. These hooks are called whenever the references of arguments to a component change. These hooks receive arguments, however one should not use them as they force those objects to reify, which can be very costly when you have a lot of components on the page. These arguments are also purposely undocumented.\n\nIf for some reason you need to do a comparison of arguments we suggest that you simply keep a cache on the component.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default Component({\n  init(...args) {\n    this._super(...args);\n    this.updated = false;\n  },\n  didReceiveAttrs(attrs) {\n    const { newAttrs, oldAttrs } = attrs;\n    if (newAttrs && oldAttrs && newAttrs.value !== oldAttrs.value) {\n      this.set('updated', true);\n    } else {\n      this.set('updated', false);\n    }\n  },\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default Component({\n  init(...args) {\n    this._super(...args);\n    this._valueCache = this.value;\n    this.updated = false;\n  },\n  didReceiveAttrs() {\n    if (this._valueCache === this.value) {\n      this.set('updated', false);\n    } else {\n      this._valueCache = this.value;\n      this.set('updated', true);\n    }\n  },\n});\n```\n\n## When Not To Use It\n\nYou can turn this rule of if you aren't worried about the negative performance impacts of using params in these hooks.\n\n## Further Reading\n\n- [`didReceiveAttrs`](https://guides.emberjs.com/v2.9.0/components/the-component-lifecycle/#toc_formatting-component-attributes-with-code-didreceiveattrs-code)\n- [`didUpdateAttrs`](https://guides.emberjs.com/v2.9.0/components/the-component-lifecycle/#toc_resetting-presentation-state-on-attribute-change-with-code-didupdateattrs-code)\n"
  },
  {
    "path": "docs/rules/no-builtin-form-components.md",
    "content": "# ember/no-builtin-form-components\n\n<!-- end auto-generated rule header -->\n\nThis rule disallows the use of Ember's built-in form components (`Input` and `Textarea`) from `@ember/component` and encourages using native HTML elements instead.\n\n## Rule Details\n\nEmber's built-in form components (`Input` and `Textarea`) were designed to bridge the gap between classic HTML form elements and Ember's component system. However, as Ember has evolved, using native HTML elements with modifiers has become the preferred approach for several reasons:\n\n- Native HTML elements have better accessibility support\n- They provide a more consistent developer experience with standard web development\n- They have better performance characteristics\n- They avoid the extra abstraction layer that the built-in components provide\n\nThis rule helps identify where these built-in form components are being used so they can be replaced with native HTML elements.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { Input } from '@ember/component';\n```\n\n```js\nimport { Textarea } from '@ember/component';\n```\n\n```js\nimport { Input as EmberInput, Textarea as EmberTextarea } from '@ember/component';\n```\n\nExamples of **correct** code for this rule:\n\n```hbs\n<!-- Instead of using the Input component -->\n<input value={{this.value}} {{on 'input' this.updateValue}} />\n\n<!-- Instead of using the Textarea component -->\n<textarea value={{this.value}} {{on 'input' this.updateValue}} />\n```\n\n## Migration\n\n### Input Component\n\nReplace:\n\n```hbs\n<Input\n  @value={{this.value}}\n  @type='text'\n  @placeholder='Enter text'\n  {{on 'input' this.handleInput}}\n/>\n```\n\nWith:\n\n```hbs\n<input value={{this.value}} type='text' placeholder='Enter text' {{on 'input' this.handleInput}} />\n```\n\n### Textarea Component\n\nReplace:\n\n```hbs\n<Textarea @value={{this.value}} @placeholder='Enter text' {{on 'input' this.handleInput}} />\n```\n\nWith:\n\n```hbs\n<textarea value={{this.value}} placeholder='Enter text' {{on 'input' this.handleInput}} />\n```\n\n## References\n\n- [Ember Input Component API](https://api.emberjs.com/ember/release/classes/Input)\n- [Ember Textarea Component API](https://api.emberjs.com/ember/release/classes/Textarea)\n- [Ember Octane Modifier RFC](https://emberjs.github.io/rfcs/0373-element-modifiers.html)\n"
  },
  {
    "path": "docs/rules/no-capital-letters-in-routes.md",
    "content": "# ember/no-capital-letters-in-routes\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nRaise an error when there is a route with upper-cased letters in router.js.\n\nWhen you accidentally uppercase any of your routes or create upper-cased route using ember-cli the application will crash without any clear information what's wrong. This rule makes it more obvious, so you don't have to think about it any more.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nthis.route('Home');\nthis.route('SignUp');\n```\n\nExamples of **correct** code for this rule:\n\n```js\nthis.route('home');\nthis.route('sign-up');\n```\n\n## References\n\n- [Ember Routing Guide](https://guides.emberjs.com/release/routing/)\n\n## Related Rules\n\n- [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md)\n- [route-path-style](route-path-style.md)\n- [routes-segments-snake-case](routes-segments-snake-case.md)\n"
  },
  {
    "path": "docs/rules/no-classic-classes.md",
    "content": "# ember/no-classic-classes\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow \"classic\" classes in favor of native JS classes.\n\nEmber now allows you to use native JS classes to extend the built-in classes provided by Ember. This pattern is preferred in favor of using the \"classic\" style of classes that Ember has provided since before JS classes were available to use.\n\n## Rule Details\n\nThis rule aims to ensure that you do not use a \"classic\" Ember class where a native class could be used instead. The one instance where `.extend` should still be used is for including a Mixin into your class, which does not have a native JS class alternative available.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// Extending an Ember class using the \"classic\" class pattern is not OK\nimport Component from '@ember/component';\n\nexport default Component.extend({});\n```\n\n```js\n// With option: additionalClassImports = ['my-custom-addon']\nimport CustomClass from 'my-custom-addon';\n\nexport default CustomClass.extend({});\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// Extending using a native JS class is OK\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {}\n```\n\n```js\n// Including a Mixin is OK\nimport Component from '@ember/component';\nimport Evented from '@ember/object/evented';\n\nexport default class MyComponent extends Component.extend(Evented) {}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                     | Description                                                                                                                                                                                                        | Type     |\n| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- |\n| `additionalClassImports` | Allows you to specify additional imports that should be flagged to disallow calling `extend` on. This allows you to handle the case where your app or addon is importing from a module that performs the `extend`. | String[] |\n\n<!-- end auto-generated rule options list -->\n\n## When Not To Use It\n\n- If you are not ready to transition completely to native JS classes, you should not enable this rule\n\n## Further Reading\n\n- [Ember Octane Release Plan](https://blog.emberjs.com/2019/08/15/octane-release-plan.html)\n  - Includes advice on transition Components to use native, rather than classic, classes\n- [Ember.js Native Class Update - 2019 Edition](https://blog.emberjs.com/2019/01/26/emberjs-native-class-update-2019-edition.html)\n  - **Note:** some of the recommendations made in this blog post are longer relevant, such as using `.extend` when defining a computed property\n"
  },
  {
    "path": "docs/rules/no-classic-components.md",
    "content": "# ember/no-classic-components\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule aims to enforce Glimmer components instead of classic ones. We should migrate to Glimmer components because\nthey have few advantages:\n\n- Simpler API\n- No wrapper element\n- Namespaced Arguments\n- Less lifecycle hooks\n- Stateless Template-Only Components\n- Unidirectional Dataflow\n\nWith that simpler API we could improve the DX and also lower the entry level for Ember.\n\n## Rule Details\n\nIf you want to migrate to Glimmer components this rule can help find the classic components that you need to migrate.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@glimmer/component';\n```\n\n## References\n\n- [Ember 3.13 Release Notes](https://blog.emberjs.com/2019/09/25/ember-3-13-released.html) (minimum Ember version needed to use Glimmer components)\n- [Ember Glimmer Components RFC](https://github.com/emberjs/rfcs/blob/master/text/0416-glimmer-components.md)\n- [Ember Octane Release Plan](https://blog.emberjs.com/2019/08/15/octane-release-plan.html)\n- [Glimmer Components Explained](https://www.pzuraq.com/coming-soon-in-ember-octane-part-5-glimmer-components/)\n"
  },
  {
    "path": "docs/rules/no-component-lifecycle-hooks.md",
    "content": "# ember/no-component-lifecycle-hooks\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow usage of \"classic\" ember component lifecycle hooks.\n\n## Rule Details\n\nAs most component lifecycle hooks are gone in glimmer components, this rule aims to:\n\n- remind the developer that classic Ember component lifecycle hooks no longer exist in glimmer components\n- encourage migrating away from classic Ember component lifecycle hooks in classic ember components\n\nCustom functional modifiers or @ember/render-modifiers should be used instead.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  // Classic Ember component lifecycle hooks (minus willDestroy which is also a Glimmer component lifecycle hook):\n  didDestroyElement() {}\n  didInsertElement() {}\n  didReceiveAttrs() {}\n  didRender() {}\n  didUpdate() {}\n  didUpdateAttrs() {}\n  willClearRender() {}\n  willDestroyElement() {}\n  willInsertElement() {}\n  willRender() {}\n  willUpdate() {}\n}\n```\n\n```js\nimport GlimmerComponent from '@glimmer/component';\n\nexport default class MyComponent extends GlimmerComponent {\n  didInsertElement() {} // This is a classic Ember component lifecycle hook which can't be used in a Glimmer component.\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  init(...args) {\n    this._super(...args);\n  }\n  willDestroy() {} // Both a classic and Glimmer component lifecycle hook\n}\n```\n\n```js\nimport GlimmerComponent from '@glimmer/component';\n\nexport default class MyComponent extends GlimmerComponent {\n  // Glimmer component lifecycle hooks:\n  constructor(...args) {\n    super(...args);\n  }\n  willDestroy() {}\n}\n```\n\n## Further Reading\n\n- [`@ember/render-modifiers`](https://github.com/emberjs/ember-render-modifiers)\n- [Blog post about modifiers](https://blog.emberjs.com/2019/03/06/coming-soon-in-ember-octane-part-4.html)\n- [Classic component lifecycle hooks](https://guides.emberjs.com/v3.4.0/components/the-component-lifecycle/#toc_order-of-lifecycle-hooks-called)\n- [Glimmer component lifecycle hooks](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/#toc_lifecycle-and-properties)\n"
  },
  {
    "path": "docs/rules/no-computed-properties-in-native-classes.md",
    "content": "# ember/no-computed-properties-in-native-classes\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nSince the beginning of Ember's existence, Computed Properties (CPs) have been used to accomplish reactivity in the framework. With Ember Octane, new features were introduced including Glimmer components, native JavaScript classes and Tracked Properties. With Ember Octane's new programming model, CPs are no longer needed. If using native JavaScript classes, Tracked Properties should be used instead as they give us the same benefit of CPs but with less boilerplate and more flexibility.\n\n## Rule Details\n\nThis rule disallows using computed properties with native classes.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { and, or, alias } from '@ember/object/computed';\n\nexport default class MyComponent extends Component {\n  // ...\n}\n```\n\n```js\nimport Component from '@ember/component';\nimport { computed } from '@ember/object';\n\nexport default class MyComponent extends Component {\n  // ...\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { computed } from '@ember/object';\nimport Component from '@ember/component';\n\nexport default Component.extend({});\n```\n\n```js\nimport { alias, or, and } from '@ember/object/computed';\nimport Component from '@ember/component';\n\nexport default Component.extend({});\n```\n\n```js\n// Allowed if `ignoreClassic` option is enabled.\nimport { computed } from '@ember/object';\nimport { alias, or, and } from '@ember/object/computed';\nimport Component from '@ember/component';\nimport classic from 'ember-classic-decorator';\n\n@classic\nexport default class MyComponent extends Component {}\n```\n\n```js\nimport { tracked } from '@glimmer/tracking';\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name            | Description                                                                            | Type    | Default |\n| :-------------- | :------------------------------------------------------------------------------------- | :------ | :------ |\n| `ignoreClassic` | Whether the rule should ignore usage inside of native classes labeled with `@classic`. | Boolean | `true`  |\n\n<!-- end auto-generated rule options list -->\n\n## References\n\n- [Ember 3.13 Release Notes](https://blog.emberjs.com/2019/09/25/ember-3-13-released.html) (minimum Ember version needed to use tracked properties)\n- [Ember Guides: Tracked Properties](https://octane-guides-preview.emberjs.com/release/state-management/tracked-properties/)\n- [Tracked Properties Deep Dive](https://www.pzuraq.com/coming-soon-in-ember-octane-part-3-tracked-properties/)\n- [ember-native-class-codemod](https://github.com/ember-codemods/ember-native-class-codemod)\n- [ember-classic-decorator](https://github.com/emberjs/ember-classic-decorator)\n"
  },
  {
    "path": "docs/rules/no-controller-access-in-routes.md",
    "content": "# ember/no-controller-access-in-routes\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nAccessing the controller in a route outside of `setupController`/`resetController` hooks (where it is passed as an argument) is discouraged.\n\nIf access is required regardless, `controllerFor` must be used to assert the controller isn't undefined as it is not guaranteed to be eagerly loaded (for optimization purposes).\n\n## Rule Details\n\nThis rule disallows routes from accessing the controller outside of `setupController`/`resetController`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Route from '@ember/routing/route';\nimport { action } from '@ember/object';\n\nexport default class MyRoute extends Route {\n  @action\n  myAction() {\n    const controller = this.controller;\n  }\n}\n```\n\n```js\nimport Route from '@ember/routing/route';\nimport { action } from '@ember/object';\n\nexport default class MyRoute extends Route {\n  @action\n  myAction() {\n    const controller = this.controllerFor('my');\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Route from '@ember/routing/route';\n\nexport default class MyRoute extends Route {\n  setupController(controller, ...args) {\n    super.setupController(controller, ...args);\n    const foo = controller.foo;\n  }\n}\n```\n\n```js\nimport Route from '@ember/routing/route';\n\nexport default class MyRoute extends Route {\n  resetController(controller, ...args) {\n    super.resetController(controller, ...args);\n    const foo = controller.foo;\n  }\n}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                 | Description                                                                                                                                        | Type    | Default |\n| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `allowControllerFor` | Whether the rule should allow or disallow routes from accessing the controller outside of `setupController`/`resetController` via `controllerFor`. | Boolean | `false` |\n\n<!-- end auto-generated rule options list -->\n"
  },
  {
    "path": "docs/rules/no-controllers.md",
    "content": "# ember/no-controllers\n\n<!-- end auto-generated rule header -->\n\nSome people may prefer to avoid the use of controllers in their applications, typically in favor of components which can be more portable and easier to test.\n\nThis rule disallows controller usage, except when the controller is used to define `queryParams` (a feature currently only available in controllers).\n\nNote: this rule will not be added to the `recommended` configuration until controller usage has become less common / deprecated.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Controller from '@ember/controller';\n\nexport default class ArticlesController extends Controller {\n  // Controller disallowed since it doesn't use `queryParams`.\n  // ...\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Controller from '@ember/controller';\n\nexport default class ArticlesController extends Controller {\n  queryParams = ['category']; // Controller allowed for defining `queryParams`.\n  @tracked category = null;\n  // ...\n}\n```\n\n## Further Reading\n\n- [Guide on controllers](https://guides.emberjs.com/release/routing/controllers/)\n- [Guide on `queryParameters`](https://guides.emberjs.com/release/routing/query-params/)\n"
  },
  {
    "path": "docs/rules/no-current-route-name.md",
    "content": "# ember/no-current-route-name\n\n<!-- end auto-generated rule header -->\n\nThe route name is something which is not visible to the user, so it can be\nconsidered an implementation detail. The URL however is visible to the user, so\nwhen writing tests it makes much more sense to assert against `currentURL()`\ninstead of `currentRouteName()`.\n\n## Rule Details\n\nThis rule warns about any usage of the `currentRouteName()` test helper.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nassert.equal(currentRouteName(), 'foo');\n```\n\nExamples of **correct** code for this rule:\n\n```js\nassert.equal(currentURL(), '/foo');\n```\n\n## Migration\n\n- Replace `currentRouteName()` with `currentURL()` and adjust\n  the assertion expectations\n\n## References\n\n- [currentRouteName()](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#currentroutename) documentation\n- [currentURL()](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#currenturl) documentation\n"
  },
  {
    "path": "docs/rules/no-deeply-nested-dependent-keys-with-each.md",
    "content": "# ember/no-deeply-nested-dependent-keys-with-each\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows usage of deeply-nested computed property dependent keys with `@each`.\n\nFor performance / complexity reasons, Ember does not allow deeply-nested computed property dependent keys with `@each`. At runtime, it will show a warning about this:\n\n> WARNING: Dependent keys containing @each only work one level deep. You used the key `\"foo.@each.bar.baz\"` which is invalid. Please create an intermediary computed property.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  displayNames: computed('todos.@each.owner.name', function () {\n    return this.todos.map((todo) => todo.owner.name);\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  displayNames: computed('owners.@each.name', function () {\n    return this.owners.mapBy('name');\n  }),\n\n  owners: computed('todos.@each.owner', function () {\n    return this.todos.mapBy('owner');\n  }),\n});\n```\n\n## Further Reading\n\n- See the [documentation](https://guides.emberjs.com/release/object-model/computed-properties-and-aggregate-data/) for Ember computed properties with `@each`\n"
  },
  {
    "path": "docs/rules/no-deprecated-router-transition-methods.md",
    "content": "# ember/no-deprecated-router-transition-methods\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEmber 3.26 introduced a deprecation for using `transitionTo` and `replaceWith` in Routes or `transitionToRoute` and `replaceRoute` in Controllers. These methods should be replaced with an injected router service and calls to `this.router.transitionTo` and `this.router.replaceWith` instead.\n\n## Rule Details\n\nThis rule checks for uses of `transitionTo` and `replaceWith` in Routes or `transitionToRoute` and `replaceRoute` in Controllers.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// app/routes/settings.js\nimport Route from '@ember/routing/route';\nimport { inject as service } from '@ember/service';\n\nexport default class SettingsRoute extends Route {\n  @service session;\n\n  beforeModel() {\n    if (!this.session.isAuthenticated) {\n      this.transitionTo('login');\n    }\n  }\n}\n```\n\n```js\n// app/controllers/new-post.js\nimport Controller from '@ember/controller';\nimport { action } from '@ember/object';\n\nexport default class NewPostController extends Controller {\n  @action\n  async save({ title, text }) {\n    const post = this.store.createRecord('post', { title, text });\n    await post.save();\n    return this.transitionToRoute('post', post.id);\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// app/routes/settings.js\nimport Route from '@ember/routing/route';\nimport { inject as service } from '@ember/service';\n\nexport default class SettingsRoute extends Route {\n  @service('router') router;\n  @service('session') session;\n\n  beforeModel() {\n    if (!this.session.isAuthenticated) {\n      this.router.transitionTo('login');\n    }\n  }\n}\n```\n\n```js\n// app/controllers/new-post.js\nimport Controller from '@ember/controller';\nimport { action } from '@ember/object';\nimport { inject as service } from '@ember/service';\n\nexport default class NewPostController extends Controller {\n  @service('router') router;\n\n  @action\n  async save({ title, text }) {\n    const post = this.store.createRecord('post', { title, text });\n    await post.save();\n    return this.router.transitionTo('post', post.id);\n  }\n}\n```\n\n## Migration\n\nThe autofixer for this rule will update method calls to use the router service, and will inject the router service as needed.\n\n## References\n\n- [Deprecation](https://deprecations.emberjs.com/v3.x/#toc_routing-transition-methods)\n- [Router Service](https://api.emberjs.com/ember/release/classes/RouterService)\n"
  },
  {
    "path": "docs/rules/no-duplicate-dependent-keys.md",
    "content": "# ember/no-duplicate-dependent-keys\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow repeating dependent keys.\n\n## Rule Details\n\nThis rule makes it easy to spot repeating dependent keys in computed properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\ncomputed('foo.bar', 'foo.baz', 'foo.qux', 'foo.bar', function () {\n  // ...\n});\n// or using brace expansions\ncomputed('foo.{bar,baz,qux}', 'foo.bar', function () {\n  // ...\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\ncomputed('foo.bar', 'foo.baz', 'foo.qux', function () {\n  // ...\n});\n// or using brace expansions\ncomputed('foo.{bar,baz,qux}', 'bar.foo', function () {\n  // ...\n});\n```\n"
  },
  {
    "path": "docs/rules/no-ember-super-in-es-classes.md",
    "content": "# ember/no-ember-super-in-es-classes\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n`this._super` is not allowed in ES class methods.\n\nWhile `this._super()` is the only way to invoke an overridden method in an `EmberObject.extend`-style class, the `_super` method doesn't work properly when using native class syntax. Fortunately, native classes come with their own mechanism for invoking methods from a parent.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  init(...args) {\n    this._super(...args);\n    // Other logic\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  init(...args) {\n    super.init(...args);\n    // Other logic\n  }\n}\n```\n\n```js\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    // Other logic\n  },\n});\n```\n"
  },
  {
    "path": "docs/rules/no-ember-testing-in-module-scope.md",
    "content": "# ember/no-ember-testing-in-module-scope\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n`Ember.testing` is not allowed in modules scope.\n\nSince ember-cli-qunit@4.1.0 / ember-qunit@3.0.0, `Ember.testing` is only set to\ntrue while a test is executing instead of all the time. Also, `Ember.testing` is a\ngetter/setter in Ember and destructuring will only read its value at the time\nof destructuring.\n\n<https://github.com/emberjs/ember-test-helpers/pull/227>\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    this.isTesting = Ember.testing;\n  },\n});\n```\n\n```js\nimport Ember from 'ember';\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  isTesting: Ember.testing,\n});\n```\n\n```js\nimport Ember from 'ember';\n\nconst IS_TESTING = Ember.testing;\n```\n\n```js\nimport Ember from 'ember';\n\nconst { testing } = Ember;\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Ember from 'ember';\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  someMethod() {\n    if (Ember.testing) {\n      doSomething();\n    } else {\n      doSomethingElse();\n    }\n  },\n});\n```\n\n```js\nimport Ember from 'ember';\nimport Service from '@ember/service';\n\nexport default Service.extend({\n  foo() {\n    _bar(Ember.testing ? 0 : 400);\n  },\n});\n```\n"
  },
  {
    "path": "docs/rules/no-empty-attrs.md",
    "content": "# ember/no-empty-attrs\n\n<!-- end auto-generated rule header -->\n\nBe explicit with Ember data attribute types.\n\nEmber Data handles not specifying a transform in model description. Nonetheless this could lead to ambiguity. This rule ensures that the right transform is specified for every attribute.\n\nNote: this rule is not in the `recommended` configuration because the Ember Data team recommends not using transforms unless you actually want to transform something.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nconst { Model, attr } = DS;\n\nexport default Model.extend({\n  name: attr(),\n  points: attr(),\n  dob: attr(),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nconst { Model, attr } = DS;\n\nexport default Model.extend({\n  name: attr('string'),\n  points: attr('number'),\n  dob: attr('date'),\n});\n```\n\nIn case you need a custom behavior, it's good to write your own [transform](https://api.emberjs.com/ember-data/release/classes/Transform).\n"
  },
  {
    "path": "docs/rules/no-empty-glimmer-component-classes.md",
    "content": "# ember/no-empty-glimmer-component-classes\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule will catch and prevent the use of empty backing classes for Glimmer components.\n\n## Rule Details\n\nThis rule aims to disallow the use of empty backing classes for Glimmer components when possible including only using ember template tags in your Glimmer component. Template-only Glimmer components where there is no backing class are much faster and lighter-weight than Glimmer components with backing classes, which are much lighter-weight than Ember components. Therefore, you should only have a backing class for a Glimmer component when absolutely necessary. An exception to this case is if you need the component to be generic over part of its type signature.\n\nTo fix violations of this rule:\n\n- In apps: Remove the backing class entirely until it is actually needed.\n- In in-repo addons: Replace the backing class depending on what the host app is doing. That is, if `template-only-glimmer-components` is enabled, remove the backing class. Otherwise, replace it with a `templateOnly` export.\n- In other addons: Replace the backing class with a `templateOnly` export. This is necessary because you can't assume `template-only-glimmer-components` is enabled.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@glimmer/component';\n\nclass MyComponent extends Component {}\n```\n\n```js\nimport Component from '@glimmer/component';\n\nexport default class MyComponent extends Component {\n  <template>Hello World!</template>\n}\n```\n\n```ts\nimport Component from '@glimmer/component';\n\nexport interface TypeSig {}\n\nexport default class MyComponent extends Component<TypeSig> {}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@glimmer/component';\n\nclass MyComponent extends Component {\n  foo() {\n    return this.args.bar + this.args.baz;\n  }\n}\n```\n\n```js\nimport templateOnly from '@ember/component/template-only';\n\nconst MyComponent = templateOnly();\n\nexport default MyComponent;\n```\n\n```js\nimport Component from '@glimmer/component';\nimport MyDecorator from 'my-decorator';\n\n@MyDecorator\nclass MyComponent extends Component {}\n```\n\n```js\nimport Component from '@glimmer/component';\nimport MyDecorator from 'my-decorator';\n\n@MyDecorator\nclass MyComponent extends Component {\n  <template>foo</template>\n}\n```\n\n```js\nimport Component from '@glimmer/component';\n\nexport default class MyComponent extends Component {\n  foo() {\n    return this.args.bar + this.args.baz;\n  }\n\n  <template>Hello World!</template>\n}\n```\n\n```ts\nimport Component from '@glimmer/component';\n\nexport interface SomeSig {}\nexport interface SomeOtherSig {}\n\nexport default class MyComponent<SomeSig> extends Component<SomeOtherSig> {}\n```\n\n## References\n\n- [Glimmer Components - Octane Upgrade Guide](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/)\n- [Glimmer Components RFC](https://emberjs.github.io/rfcs/0416-glimmer-components.html)\n- [First-Class Component Templates RFC](https://rfcs.emberjs.com/id/0779-first-class-component-templates/)\n"
  },
  {
    "path": "docs/rules/no-function-prototype-extensions.md",
    "content": "# ember/no-function-prototype-extensions\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nBy default, Ember extends certain native JavaScript objects with additional methods. This can lead to problems in some situations. One example is relying on these methods in an addon that is used inside an app that has the extensions disabled.\n\nThe prototype extensions for the `function` object were deprecated in [RFC #272](https://rfcs.emberjs.com/id/0272-deprecation-native-function-prototype-extensions).\n\nUse computed property syntax, observer syntax, or module hooks instead of `.property()`, `.observes()` or `.on()` in Ember modules.\n\n## Rule Details\n\nThis rule will disallow method calls that match any of the forbidden `function` prototype extension method names.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  abc: function () {\n    /* custom logic */\n  }.property('xyz'),\n  def: function () {\n    /* custom logic */\n  }.observes('xyz'),\n  ghi: function () {\n    /* custom logic */\n  }.on('didInsertElement'),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  abc: computed('xyz', function () {\n    /* custom logic */\n  }),\n  def: observer('xyz', function () {\n    /* custom logic */\n  }),\n  didInsertElement() {\n    /* custom logic */\n  },\n});\n```\n\n## References\n\n- [Ember prototype extensions documentation](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/)\n- [Ember function prototype extensions deprecation RFC](https://rfcs.emberjs.com/id/0272-deprecation-native-function-prototype-extensions)\n\n## Related Rules\n\n- [no-array-prototype-extensions](no-array-prototype-extensions.md)\n- [no-string-prototype-extensions](no-string-prototype-extensions.md)\n"
  },
  {
    "path": "docs/rules/no-get-with-default.md",
    "content": "# ember/no-get-with-default\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule attempts to catch and prevent the use of `getWithDefault`.\n\n## Rule Details\n\nEven though the behavior for `getWithDefault` is more defined such that it only falls back to the default value on `undefined`, its inconsistency with the native `||` is confusing to many developers who assume otherwise. Instead, this rule encourages developers to use:\n\n- `||` operator\n- ternary operator\n\nIn addition, [Nullish Coalescing Operator `??`](https://github.com/tc39/proposal-nullish-coalescing) will land in the JavaScript language soon so developers can leverage safe property access with native support instead of using `getWithDefault`. But note that `??` checks for either `undefined` or `null` whereas `getWithDefault` only checks for `undefined`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nconst test = this.getWithDefault('key', []);\n```\n\n```js\nimport { getWithDefault } from '@ember/object';\n\nconst test = getWithDefault(this, 'key', []);\n```\n\nExamples of **correct** code for this rule:\n\n```js\nconst test = this.key === undefined ? [] : this.key;\n```\n\n```js\n// the behavior of this is different because `test` would be assigned `[]` on any falsy value instead of on only `undefined`.\nconst test = this.key || [];\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                 | Description                                                                                                                                                 | Type    | Default |\n| :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `catchSafeObjects`   | Whether the rule should catch non-`this` imported usages like `getWithDefault(person, 'name', '')`.                                                         | Boolean | `true`  |\n| `catchUnsafeObjects` | Whether the rule should catch non-`this` usages like `person.getWithDefault('name', '')` even though we don't know for sure if `person` is an Ember object. | Boolean | `true`  |\n\n<!-- end auto-generated rule options list -->\n\n## References\n\n- [RFC](https://github.com/emberjs/rfcs/pull/554/) to deprecate `getWithDefault`\n- [spec](https://api.emberjs.com/ember/3.13/functions/@ember%2Fobject/getWithDefault)\n\n## Related Rules\n\n- [no-get](no-get.md)\n"
  },
  {
    "path": "docs/rules/no-get.md",
    "content": "# ember/no-get\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nStarting in Ember 3.1, native ES5 getters are available, which eliminates much of the need to use `get` / `getProperties` on Ember objects.\n\n## Rule Details\n\nThis rule disallows:\n\n- `this.get('someProperty')` when `this.someProperty` can be used\n- `this.getProperties('prop1', 'prop2')` when `{ prop1: this.prop1, prop2: this.prop2 }` can be used\n\n**WARNING**: there are a number of circumstances where `get` / `getProperties` still need to be used, and you may need to manually disable the rule for these (although the rule will attempt to ignore them):\n\n- Ember proxy objects (`ObjectProxy`, `ArrayProxy`)\n- Objects implementing the `unknownProperty` method\n\nIn addition, `mirage/config.js` will be excluded from this rule.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nconst foo = this.get('someProperty');\n```\n\n```js\nimport { get } from '@ember/object';\n\nconst foo = get(this, 'someProperty');\n```\n\n```js\nconst { prop1, prop2 } = this.getProperties('prop1', 'prop2');\n```\n\n```js\nimport { getProperties } from '@ember/object';\n\nconst foo = getProperties(this, 'prop1', 'prop2');\n```\n\nExamples of **correct** code for this rule:\n\n```js\nconst foo = this.someProperty;\n```\n\n```js\nconst foo = this.nested?.path; // Optional chaining can be useful if the nested path can have null or undefined properties in it.\n```\n\n```js\nconst foo = this.get('some.nested.property'); // Allowed if `ignoreNestedPaths` option is enabled.\n```\n\n```js\nconst { prop1, prop2 } = this;\n```\n\n```js\nconst foo = { prop1: this.prop1, prop2: this.prop2 };\n```\n\n```js\nimport ObjectProxy from '@ember/object/proxy';\n\nexport default ObjectProxy.extend({\n  someFunction() {\n    const foo = this.get('propertyInsideProxyObject'); // Allowed because inside proxy object.\n  },\n});\n```\n\n```js\nimport EmberObject from '@ember/object';\n\nexport default EmberObject.extend({\n  unknownProperty(key) {},\n  someFunction() {\n    const foo = this.get('property'); // Allowed because inside object implementing `unknownProperty()`.\n  },\n});\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                  | Description                                                                                                                                                                                                                                                                                                                                   | Type    | Default |\n| :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `catchSafeObjects`    | Whether the rule should catch non-`this` imported usages like `get(foo, 'bar')`.                                                                                                                                                                                                                                                              | Boolean | `true`  |\n| `catchUnsafeObjects`  | Whether the rule should catch non-`this` usages like `foo.get('bar')` even though we don't know for sure if `foo` is an Ember object.                                                                                                                                                                                                         | Boolean | `false` |\n| `ignoreGetProperties` | Whether the rule should ignore `getProperties`.                                                                                                                                                                                                                                                                                               | Boolean | `false` |\n| `ignoreNestedPaths`   | Whether the rule should ignore `this.get('some.nested.property')` (can't be enabled at the same time as `useOptionalChaining`).                                                                                                                                                                                                               | Boolean | `false` |\n| `useAt`               | Whether the rule should use `at(-1)` [Array.prototype.at()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at) to replace `lastObject`.                                                                                                                                                               | Boolean | `true`  |\n| `useOptionalChaining` | Whether the rule should use the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) `?.` to autofix nested paths such as `this.get('some.nested.property')` to `this.some?.nested?.property` (when this option is off, these nested paths won't be autofixed at all). | Boolean | `true`  |\n\n<!-- end auto-generated rule options list -->\n\n## Related Rules\n\n- [no-proxies](no-proxies.md)\n\n## References\n\n- [Ember 3.1 Release Notes](https://blog.emberjs.com/2018/04/13/ember-3-1-released.html) describing \"ES5 Getters for Computed Properties\"\n- [Ember get Spec](https://api.emberjs.com/ember/release/functions/@ember%2Fobject/get)\n- [Ember getProperties Spec](https://api.emberjs.com/ember/release/functions/@ember%2Fobject/getProperties)\n- [Ember ES5 Getter RFC](https://github.com/emberjs/rfcs/blob/master/text/0281-es5-getters.md)\n- [es5-getter-ember-codemod](https://github.com/rondale-sc/es5-getter-ember-codemod)\n- [More context](https://github.com/emberjs/ember.js/issues/16148) about the proxy object exception to this rule\n"
  },
  {
    "path": "docs/rules/no-global-jquery.md",
    "content": "# ember/no-global-jquery\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDo not use global `$` or `jQuery`.\n\n## Rule Details\n\nIn general, we want application code to reference the version of jQuery that's been directly pinned to the version of Ember used. This helps avoid version conflicts, and ensures that code inside modules isn't reliant on global variables.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    $('.foo').addClass('bar'); // global usage\n  },\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Ember from 'ember';\n\nconst { $ } = Ember;\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    Ember.$('.foo').addClass('bar'); // usage from Ember object\n    // or even better\n    $('.foo').addClass('bar'); // deconstruction from Ember object\n  },\n});\n```\n\n## Related\n\n- [jquery-ember-run](./jquery-ember-run.md)\n- [no-jquery](./no-jquery.md)\n"
  },
  {
    "path": "docs/rules/no-html-safe.md",
    "content": "# ember/no-html-safe\n\n<!-- end auto-generated rule header -->\n\n`htmlSafe` marks a string as safe for unescaped output with Ember templates so you can render it as HTML. `htmlSafe` does **not** perform input sanitization. While useful this can inadvertently open you up to Cross-site Scripting (XSS) vulnerabilities, especially if the string was generated from user input or some other untrusted source. **You should only ever use `htmlSafe` with trusted or sanitized input**.\n\nNote: this rule is not in the `recommended` configuration because there are legitimate usages of `htmlSafe`.\n\n## Rule Details\n\nThis rule prevents importing the `htmlSafe` utillity from `@ember/template` (or `@ember/string` for older Ember versions);\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { htmlSafe } from '@ember/template';\n```\n\n```js\nimport { htmlSafe } from '@ember/string';\n```\n\n## Alternatives\n\nThere are a few alternative strategies to using `htmlSafe`.\n\n### Scenario\n\nIn this example we have a property `userContent` which contains a user-generated string of HTML. For example, something like `<h1>Joe Bloggs</h1>`. This is then rendered directly into the template.\n\n```js\n// components/my-component.js\nimport { htmlSafe } from '@ember/template';\n\nclass MyComponent extends Component {\n  get myContent() {\n    return htmlSafe(this.args.userContent);\n  }\n}\n```\n\n```hbs\n{{! components/my-component.hbs }}\n{{this.myContent}}\n```\n\n### Alternative 1: Render the HTML yourself\n\nWhile not as flexible, if you can control the content generated by the user you should only let the user enter plaintext and render the HTML yourself.\n\n```hbs\n{{! components/my-component.hbs }}\n<h1>{{@userContent}}</h1>\n```\n\n### Alternative 2: Define your own trusted sanitizing helper\n\nIf you _have_ to render user generated HTML, you should protect yourself by always sanitizing the input first. One strategy is to, rather than have usage of `htmlSafe` proliferated throughout your app, isolate it to a single location and combine it with a sanitization library, e.g. [DOMPurify](https://github.com/cure53/DOMPurify). Then require all HTML strings go through this helper. If this helper is in the same codebase where the `no-html-safe` lint rule is enabled, you can disable the rule for this single location.\n\n```js\n// app/lib/sanitized-content.js\nimport { htmlSafe } from '@ember/template';\n\nexport function sanitizedContent(content) {\n  const sanitized = someSanitizationLibrary(content);\n  return htmlSafe(sanitized);\n}\n```\n\n```js\n// app/components/my-component.js\nimport { sanitizedContent } from 'my-app/lib/sanitized-content';\n\nclass MyComponent extends Component {\n  get myContent() {\n    return sanitizedContent(this.args.userContent);\n  }\n}\n```\n\n```hbs\n{{! components/my-component.hbs }}\n{{this.myContent}}\n```\n\n### Alternative 3: Define your own trusted sanitizing Handlebars helper\n\nThis is similar to the previous example but in this case you could create a Handlebars helper which sanitizes your input.\n\n```js\n// app/helpers/sanitize.js\nimport { htmlSafe } from '@ember/template';\nimport { helper } from '@ember/component/helper';\n\nfunction sanitize([content]) {\n  const sanitized = someSanitizationLibrary(content);\n  return htmlSafe(sanitized);\n}\n\nexport default helper(substring);\n```\n\n```hbs\n{{! components/my-component.hbs }}\n{{sanitize @userContent}}\n```\n\n## Related Rules\n\n- ember-template-lint has a [no-triple-curlies](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-triple-curlies.md) rule for the template equivalent of this rule.\n\n## References\n\n- [Ember's `htmlSafe` API documentation](https://api.emberjs.com/ember/release/functions/@ember%2Ftemplate/htmlSafe)\n- [MDN's Cross-site Scripting (XSS) documentation](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting)\n"
  },
  {
    "path": "docs/rules/no-implicit-injections.md",
    "content": "# ember/no-implicit-injections\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEmber 3.26 introduced a deprecation for relying on implicit service injections or allowing addons to implicitly inject services into all classes of certain types. Support for this is dropped in Ember 4.0.\n\nIn many applications, `this.store` from Ember Data is often used without injecting the `store` service in Controllers or Routes. Other addons may also have included implicit service injections via initializers and the `application.inject` API.\n\nTo resolve this deprecation, a service should be explicitly declared and injected using the [service injection decorator](https://api.emberjs.com/ember/3.28/functions/@ember%2Fservice/inject).\n\n## Rule Details\n\nThis rule checks for a configured list of previously auto injected services and warns if they are used in classes without explicit injected service properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// routes/index.js\nimport Route from '@ember/routing/route';\n\nexport default class IndexRoute extends Route {\n  model() {\n    return this.store.findAll('rental');\n  }\n}\n```\n\n```js\n// controllers/index.js\nimport Controller from '@ember/controller';\nimport { action } from '@ember/object';\n\nexport default class IndexController extends Controller {\n  @action\n  loadUsers() {\n    return this.store.findAll('user');\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// routes/index.js\nimport Route from '@ember/routing/route';\nimport { inject as service } from '@ember/service';\n\nexport default class IndexRoute extends Route {\n  @service('store') store;\n\n  model() {\n    return this.store.findAll('rental');\n  }\n}\n```\n\n```js\n// controller/index.js\nimport Route from '@ember/routing/route';\nimport { action } from '@ember/object';\nimport { inject as service } from '@ember/service';\n\nexport default class IndexController extends Controller {\n  @service('store') store;\n\n  @action\n  loadUsers() {\n    return this.store.findAll('user');\n  }\n}\n```\n\n## Migration\n\nThe autofixer for this rule will update classes and add injections for the configured services.\n\n## Configuration\n\nThis lint rule will search for instances of `store` used in routes or controllers by default. If you have other services that you would like to check for uses of, the configuration can be overridden.\n\n- object -- containing the following properties:\n  - array -- `denyList` -- Array of configuration objects configuring the lint rule to check for use of implicit injected services\n    - string -- `service` -- The (kebab-case) service name that should be checked for implicit injections and error if found\n    - array -- `propertyName` -- The property name where the service would be injected in classes. This defaults to the camel case version of the `service` config above.\n    - array -- `moduleNames` -- Array of string listing the types of classes (`Controller`, `Route`, `Component`, etc) to check for implicit injections. If an array is declared, only those class types will be checked for implicit injection. (Defaults to checking all Ember Module class files/types)\n\nExample config:\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/no-implicit-injections': [\n      'error',\n      {\n        denyList: [\n          // Ember Responsive Used to Auto Inject the media service in Components/Controllers\n          { service: 'media', moduleNames: ['Component', 'Controller'] },\n          // Ember CLI Flash Used to Auto Inject the flashMessages service in all modules\n          { service: 'flash-messages' },\n          // Check for uses of the store in Routes or Controllers\n          { service: 'store', moduleNames: ['Route', 'Controller'] },\n          // Check for the feature service injected as \"featureChecker\"\n          { service: 'feature', propertyName: 'featureChecker' },\n        ],\n      },\n    ],\n  },\n};\n```\n\n## References\n\n- [Deprecation](https://deprecations.emberjs.com/v3.x/#toc_implicit-injections)\n- [Ember Data Store Service](https://api.emberjs.com/ember-data/release/classes/Store)\n"
  },
  {
    "path": "docs/rules/no-implicit-service-injection-argument.md",
    "content": "# ember/no-implicit-service-injection-argument\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule disallows omitting the service name argument when injecting a service. Instead, the filename of the service should be used (i.e. `service-name` when the service lives at `app/services/service-name.js`).\n\nSome developers may prefer to be more explicit about what service is being injected instead of relying on the implicit and potentially costly lookup/normalization of the service from the property name.\n\nNote: this rule is not in the `recommended` configuration because it is somewhat of a stylistic preference and it's not always necessary to explicitly include the service injection argument.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { inject as service } from '@ember/service';\n\nexport default class Page extends Component {\n  @service() serviceName;\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { inject as service } from '@ember/service';\n\nexport default class Page extends Component {\n  @service('service-name') serviceName;\n}\n```\n\n## Related Rules\n\n- [no-unnecessary-service-injection-argument](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-unnecessary-service-injection-argument.md) is the opposite of this rule\n\n## References\n\n- Ember [Services](https://guides.emberjs.com/release/applications/services/) guide\n- Ember [inject](https://api.emberjs.com/ember/release/functions/@ember%2Fservice/inject) function spec\n"
  },
  {
    "path": "docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md",
    "content": "# ember/no-incorrect-calls-with-inline-anonymous-functions\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThe following functions keep track of the function references they have been passed:\n\n- `debounce`\n- `once`\n- `scheduleOnce`\n\nTo use them, make sure you are passing the same function reference each time. When an inline function is passed as an argument, the function reference will be different each time.\n\n## Rule Details\n\nThis rule disallows using inline anonymous functions with the `debounce`, `once`, and `scheduleOnce` methods when imported from `@ember/runloop`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { scheduleOnce, once, debounce } from '@ember/runloop';\n\nexport default Component.extend({\n  didInsertElement() {\n    this.doWork();\n    this.doWork();\n  },\n  doWork() {\n    debounce(() => {\n      /* this will run twice */\n    }, 300);\n    once(() => {\n      /* this will run twice */\n    });\n    scheduleOnce('afterRender', function () {\n      /* this will run twice */\n    });\n  },\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { scheduleOnce } from '@ember/runloop';\n\nexport default Component.extend({\n  didInsertElement() {\n    this.doWork();\n    this.doWork();\n  },\n  doWork() {\n    scheduleOnce('afterRender', this, this.deferredWork);\n  },\n  deferredWork() {\n    /* this will only run once */\n  },\n});\n```\n\n## References\n\n- [Ember debounce API Docs](https://api.emberjs.com/ember/release/functions/@ember%2Frunloop/debounce)\n- [Ember once API Docs](https://api.emberjs.com/ember/release/functions/@ember%2Frunloop/once)\n- [Ember scheduleOnce API Docs](https://api.emberjs.com/ember/release/functions/@ember%2Frunloop/scheduleOnce)\n"
  },
  {
    "path": "docs/rules/no-incorrect-computed-macros.md",
    "content": "# ember/no-incorrect-computed-macros\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule attempts to find incorrect usages of computed property macros, such as calling them with the incorrect number of arguments.\n\nIt currently only catches using the [and](https://api.emberjs.com/ember/release/functions/@ember%2Fobject%2Fcomputed/and) and [or](https://api.emberjs.com/ember/release/functions/@ember%2Fobject%2Fcomputed/or) macros with the wrong number of arguments, but may be expanded later.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { and, or } from '@ember/object/computed';\n\nexport default Component.extend({\n  macroPropertyAnd: and('someProperty'), // Not enough arguments.\n\n  macroPropertyOr: or('someProperty'), // Not enough arguments.\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { and, or, readOnly } from '@ember/object/computed';\n\nexport default Component.extend({\n  macroPropertyReadOnly: readOnly('someProperty'),\n\n  macroPropertyAnd: and('someProperty1', 'someProperty2'),\n\n  macroPropertyOr: or('someProperty1', 'someProperty2'),\n});\n```\n\n## Related Rules\n\n- [require-computed-macros](require-computed-macros.md)\n\n## References\n\n- [Guide](https://guides.emberjs.com/release/object-model/computed-properties/) for computed properties\n- [Spec](https://api.emberjs.com/ember/release/modules/@ember%2Fobject#functions-computed) for computed property macros\n"
  },
  {
    "path": "docs/rules/no-invalid-debug-function-arguments.md",
    "content": "# ember/no-invalid-debug-function-arguments\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nCatch usages of Ember&#39;s `assert()` / `warn()` / `deprecate()` functions that have the arguments passed in the wrong order.\n\nThis rule aims to catch a common mistake when using Ember's debug functions:\n\n- `assert(String description, Boolean condition)`\n- `warn(String description, Boolean condition, Object options)`\n- `deprecate(String description, Boolean condition, Object options)`\n\nWhen calling one of these functions, the author may mistakenly pass the `description` and `condition` arguments in the reverse order, and not notice because the function will be silent with a truthy string as the `condition`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { assert, warn } from '@ember/debug';\nimport { deprecate } from '@ember/application/deprecations';\n\n// ...\n\nassert(label, 'Label must be present.');\nwarn(label, 'Label must be present.', { id: 'missing-label' });\ndeprecate(title, 'Title is no longer supported.', { id: 'unwanted-title', until: 'some-version' });\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { assert, warn } from '@ember/debug';\nimport { deprecate } from '@ember/application/deprecations';\n\n// ...\n\nassert('Label must be present.', label);\nwarn('Label must be present.', label, { id: 'missing-label' });\ndeprecate('Title is no longer supported.', title, { id: 'unwanted-title', until: 'some-version' });\n```\n\n## Further Reading\n\n- See the [documentation](https://api.emberjs.com/ember/release/functions/@ember%2Fdebug/assert) for the Ember `assert` function.\n- See the [documentation](https://api.emberjs.com/ember/release/functions/@ember%2Fdebug/warn) for the Ember `warn` function.\n- See the [documentation](https://api.emberjs.com/ember/3.4/functions/@ember%2Fapplication%2Fdeprecations/deprecate) for the Ember `deprecate` function.\n"
  },
  {
    "path": "docs/rules/no-invalid-dependent-keys.md",
    "content": "# ember/no-invalid-dependent-keys\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDependent keys used for computed properties have to be valid.\n\n## Rule Details\n\nThis rule aims to avoid invalid dependent keys in computed properties.\n\nCurrently implemented checks:\n\n- Unbalanced open and closed braces. These can be hard to track for complex computed properties and are usually unchecked since the expressions are passed as Strings.\n- Unnecessary braces\n- Invalid position of `@each` or `[]`\n- Leading or trailing periods\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  // Unbalanced braces:\n  fullName: computed('user.{firstName,lastName', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  // Unnecessary braces:\n  userId: computed('user.{id}', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  // Terminal `@each`:\n  items: computed('arr.@each', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  // `[]` in the middle:\n  items: computed('arr.[].id', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  // Leading period:\n  userId: computed('.user.id', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  // Space:\n  userId: computed('user .id', {\n    // Code\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  fullName: computed('user.{firstName,lastName}', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  userId: computed('user.id', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  items: computed('arr.[]', {\n    // Code\n  }),\n});\n```\n\n```js\nexport default Component.extend({\n  items: computed('arr.@each.id', {\n    // Code\n  }),\n});\n```\n\n## References\n\n- [Guide](https://guides.emberjs.com/release/object-model/computed-properties/) for computed properties\n"
  },
  {
    "path": "docs/rules/no-invalid-test-waiters.md",
    "content": "# ember/no-invalid-test-waiters\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nPrevents invalid usage of test waiters.\n\n## Rule Details\n\nThe new test waiters APIs, found in the [ember-test-waiters](https://github.com/emberjs/ember-test-waiters) addon, have recommended best practices that ensure you are successful with their usage. This rule ensures that all usages are adhering to recommended best practices:\n\n- Used in module scope\n- Assigned to a variable\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { buildWaiter } from 'ember-test-waiters';\n\nfunction useWaiter() {\n  const myOtherWaiter = buildWaiter('the second'); // inside function\n}\n```\n\n```js\nimport { buildWaiter } from 'ember-test-waiters';\n\nbuildWaiter('the second'); // not stored in variable\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { buildWaiter } from 'ember-test-waiters';\n\nconst myWaiter = buildWaiter('waiterName');\n```\n\n## References\n\nFor more information on the new test waiters API, please visit [ember-test-waiters](https://github.com/emberjs/ember-test-waiters).\n"
  },
  {
    "path": "docs/rules/no-jquery.md",
    "content": "# ember/no-jquery\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule attempts to catch and prevent any usage of jQuery.\n\n## Rule Details\n\nIf you want to remove jQuery, this rule can help you by warning you of any usage of jQuery in your app.\n\nThat includes:\n\n- `this.$`, either on components or tests.\n- `import $ from 'jquery';`;\n- The global `$`\n- `Ember.$` or `const { $ } = Ember;`\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  didInsertElement() {\n    this.$('input').focus();\n  },\n});\n```\n\n```js\nexport default Component.extend({\n  click() {\n    $('body').addClass('expanded');\n    // or\n    Ember.$('body').addClass('expanded');\n  },\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  didInsertElement() {\n    this.element.querySelector('input').focus();\n  },\n});\n```\n\n```js\nexport default Component.extend({\n  click() {\n    document.body.classList.add('expanded');\n  },\n});\n```\n\n## Migration\n\nFor replacing `this.$` on components, you can use the native DOM counterpart `this.element`.\n\nFor replacing `this.$` on tests, check [ember-native-dom-helpers](https://github.com/cibernox/ember-native-dom-helpers).\n\nCodemods that could help:\n\n- [ember-3x-codemods](https://github.com/ember-codemods/ember-3x-codemods)\n- [ember-test-helpers-codemod](https://github.com/ember-codemods/ember-test-helpers-codemod)\n\n## RFCs\n\n- [Make jQuery optional](https://github.com/emberjs/rfcs/blob/master/text/0294-optional-jquery.md)\n- [Remove jQuery by default](https://github.com/emberjs/rfcs/blob/master/text/0386-remove-jquery.md)\n\n## Related\n\n- [jquery-ember-run](./jquery-ember-run.md)\n- [no-global-jquery](./no-global-jquery.md)\n"
  },
  {
    "path": "docs/rules/no-legacy-test-waiters.md",
    "content": "# ember/no-legacy-test-waiters\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nPrevents the use of the legacy test waiter APIs.\n\n## Rule Details\n\nThe legacy test waiters API has been superseded by the new [ember-test-waiters](https://github.com/emberjs/ember-test-waiters) addon.\nThis new addon provides an enhanced test waiter with robust debugging capabilities. Please use this in favor of the legacy waiter system, as detailed in [RFC 581](https://github.com/emberjs/rfcs/blob/master/text/0581-new-test-waiters.md).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { registerWaiter } from '@ember/test';\n\nlet counter = 0;\n\nif (DEBUG) {\n  registerWaiter(() => {\n    return counter === 0;\n  });\n}\n\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    counter++;\n    someAsync()\n      .then(() => console.log('hi'))\n      .finally(() => counter--);\n  },\n});\n```\n\n```js\nimport Component from '@ember/component';\nimport { registerWaiter, unregisterWaiter } from '@ember/test';\n\nlet counter = 0;\nconst waiter = () => {\n  return counter === 0;\n};\n\nif (DEBUG) {\n  registerWaiter(waiter);\n}\n\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    counter++;\n    someAsync()\n      .then(() => console.log('hi'))\n      .finally(() => counter--);\n  },\n\n  willDestroy() {\n    unregisterWaiter(waiter);\n  },\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { buildWaiter } from 'ember-test-waiters';\n\nconst waiter = buildWaiter('my-waiter');\n\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    const token = waiter.beginAsync();\n    someAsync()\n      .then(() => console.log('hi'))\n      .finally(() => waiter.endAsync(token));\n  },\n});\n```\n\n## References\n\nFor more information on the new test waiters API, please visit [ember-test-waiters](https://github.com/emberjs/ember-test-waiters).\n"
  },
  {
    "path": "docs/rules/no-mixins.md",
    "content": "# ember/no-mixins\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUsing mixins to share code appears easy at first. But they add significant complexity as a project grows. Furthermore, the [Octane programming model](https://guides.emberjs.com/release/upgrading/current-edition/) eliminates the need to use them in favor of native class semantics and other primitives.\n\nFor practical strategies on removing mixins see [this discourse thread](https://discuss.emberjs.com/t/best-way-to-replace-mixins/17395/2).\n\n## Rule Details\n\nThis rule disallows importing a mixin. This is different than [no-new-mixins](no-new-mixins.md), which disallows creating a mixin (e.g. `Mixin.create({})`). Both should be used in an app that wants to disallow the use of mixins.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// my-octane-component.js\nimport Component from '@ember/component';\nimport FooMixin from '../utils/mixins/foo';\n\nexport default class FooComponent extends Component.extend(FooMixin) {\n  // ...\n}\n```\n\n```js\n// my-component.js\nimport myMixin from 'my-mixin';\n\nexport default Component.extend(myMixin, {\n  aComputedProperty: computed('obj', function () {\n    return this.isValidClassName(obj.className);\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// my-utils.js\nexport function isValidClassName(classname) {\n  return Boolean(className.match('-class'));\n}\n\nexport function hideModal(obj, value) {\n  set(obj, 'isHidden', value);\n}\n```\n\n```js\n// my-component.js\nimport { isValidClassName } from 'my-utils';\n\nexport default Component.extend({\n  aComputedProperty: computed('obj', function () {\n    return isValidClassName(obj.className);\n  }),\n});\n```\n\n## References\n\n- [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)\n- [Why Are Mixins Considered Harmful?](https://raganwald.com/2016/07/16/why-are-mixins-considered-harmful.html)\n\n## Related Rules\n\n- [no-new-mixins](no-new-mixins.md)\n"
  },
  {
    "path": "docs/rules/no-modifier-argument-destructuring.md",
    "content": "# ember/no-modifier-argument-destructuring\n\n<!-- end auto-generated rule header -->\n\nDisallow destructuring of `modifier` arguments to avoid consuming tracked data too early.\n\n## Rule Details\n\nWhen using `ember-modifier`, destructuring the positional or named arguments of the modifier callback eagerly consumes tracked data. This can lead to backtracking re-render assertions when the tracked data is not actually needed until later (e.g., inside an event listener).\n\nInstead, access the arguments by index (for positional) or property (for named) inside the callback body where the data is actually needed.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { modifier } from 'ember-modifier';\n\n// Destructuring positional args\nmodifier((element, [text]) => {\n  element.addEventListener('hover', () => console.log(text));\n});\n```\n\n```js\nimport { modifier } from 'ember-modifier';\n\n// Destructuring named args\nmodifier((element, positional, { title }) => {\n  element.addEventListener('hover', () => console.log(title));\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { modifier } from 'ember-modifier';\n\n// Access positional args by index\nmodifier((element, positional) => {\n  element.addEventListener('hover', () => console.log(positional[0]));\n});\n```\n\n```js\nimport { modifier } from 'ember-modifier';\n\n// Access named args by property\nmodifier((element, positional, named) => {\n  element.addEventListener('hover', () => console.log(named.title));\n});\n```\n\n## References\n\n- [ember-modifier](https://github.com/ember-modifier/ember-modifier)\n- [Ember Autotracking](https://guides.emberjs.com/release/in-depth-topics/autotracking-in-depth/)\n"
  },
  {
    "path": "docs/rules/no-new-mixins.md",
    "content": "# ember/no-new-mixins\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUsing mixins to share code appears easy at first. But they add significant complexity as a project grows. Furthermore, the [Octane programming model](https://guides.emberjs.com/release/upgrading/current-edition/) eliminates the need to use them in favor of native class semantics and other primitives.\n\nFor practical strategies on removing mixins see [this discourse thread](https://discuss.emberjs.com/t/best-way-to-replace-mixins/17395/2).\n\nFor more details and examples of how mixins create problems down-the-line, see these excellent blog posts:\n\n- [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)\n- [Why Are Mixins Considered Harmful?](https://raganwald.com/2016/07/16/why-are-mixins-considered-harmful.html)\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// my-mixin.js\nexport default Mixin.create({\n  isValidClassName(classname) {\n    return Boolean(className.match('-class'));\n  },\n\n  hideModal(value) {\n    this.set('isHidden', value);\n  },\n});\n```\n\n```js\n// my-component.js\nimport myMixin from 'my-mixin';\n\nexport default Component.extend(myMixin, {\n  aComputedProperty: computed('obj', function () {\n    return this.isValidClassName(obj.className);\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// my-utils.js\nexport function isValidClassName(classname) {\n  return Boolean(className.match('-class'));\n}\n\nexport function hideModal(obj, value) {\n  set(obj, 'isHidden', value);\n}\n```\n\n```js\n// my-component.js\nimport { isValidClassName } from 'my-utils';\n\nexport default Component.extend({\n  aComputedProperty: computed('obj', function () {\n    return isValidClassName(obj.className);\n  }),\n});\n```\n\n## Related Rules\n\n- [no-mixins](no-mixins.md)\n"
  },
  {
    "path": "docs/rules/no-noop-setup-on-error-in-before.md",
    "content": "# ember/no-noop-setup-on-error-in-before\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows use of no-op `setupOnerror` in `before`/`beforeEach` since it could mask errors or rejections in tests unintentionally\n\n## Rule Details\n\nThis rule aims to avoid single no-op `setupOnerror` for all tests in the module. In certain situations(maybe the majority of the test cases throw an error), the author of the test might resort to the definition of single no-op `setupOnerror` in `before`/`beforeEach`. This might make sense at the time of writing the tests, but modules tend to grow and no-op error handler would swallow any promise rejection or error that otherwise would be caught by test.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { setupOnerror } from '@ember/test-helpers';\nimport { module } from 'qunit';\n\nmodule('foo', function (hooks) {\n  hooks.beforeEach(function () {\n    setupOnerror(() => {});\n  });\n});\n```\n\n```js\nimport { setupOnerror } from '@ember/test-helpers';\nimport { module } from 'qunit';\n\nmodule('foo', function (hooks) {\n  hooks.before(function () {\n    setupOnerror(() => {});\n  });\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { setupOnerror } from '@ember/test-helpers';\nimport { module, test } from 'qunit';\n\nmodule('foo', function (hooks) {\n  test('something', function () {\n    setupOnerror((error) => {\n      assert.equal(error.message, 'test', 'Should have message');\n    });\n  });\n});\n```\n"
  },
  {
    "path": "docs/rules/no-observers.md",
    "content": "# ember/no-observers\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nYou should avoid observers for the following reasons:\n\n- Observers deal with data changes contrary to Ember's Data Down, Actions Up (DDAU) convention.\n- They also are synchronous by default which [has problems](https://emberjs.github.io/rfcs/0494-async-observers.html#motivation).\n- You can usually solve state management problems using other robust tools in Ember's toolbox such as actions, computed properties, or component life cycle hooks.\n\nSee [@ef4's](https://github.com/ef4/) [canonical answer](https://discuss.emberjs.com/t/why-should-i-not-use-observers-in-my-ember-application/16868/3) for why you should not use them.\nObservers do have some limited uses. They let you reflect state from your application to foreign interfaces that don't follow Ember's data flow conventions.\n\n```hbs\n{{input value=text key-up='change'}}\n```\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { observer } from '@ember/object';\n\nexport default Controller.extend({\n  change: observer('text', function () {\n    console.log(`change detected: ${this.text}`);\n  }),\n});\n```\n\n```js\nimport { observes } from '@ember-decorators/object';\n\nclass FooComponent extends Component {\n  @observes('text')\n  change() {\n    console.log(`change detected: ${this.text}`);\n  }\n}\n```\n\n```js\nimport { addObserver, removeObserver } from '@ember/object/observers';\n\nclass FooComponent extends Component {\n  constructor(...args) {\n    super(...args);\n    addObserver(this, 'text', this.change);\n  }\n\n  change() {\n    console.log(`change detected: ${this.text}`);\n  }\n\n  willDestroy(...args) {\n    removeObserver(this, 'text', this.change);\n    super.willDestroy(...args);\n  }\n}\n```\n\n```js\nimport { inject as service } from '@ember/service';\n\nclass FooComponent extends Component {\n  @service time;\n\n  constructor(...args) {\n    super(...args);\n    this.time.addObserver('currentTime.seconds', this.update);\n  }\n\n  update() {\n    console.log(`The time is ${this.time.currentTime}`);\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Controller.extend({\n  actions: {\n    change() {\n      console.log(`change detected: ${this.text}`);\n    },\n  },\n});\n```\n"
  },
  {
    "path": "docs/rules/no-old-shims.md",
    "content": "# ember/no-old-shims\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDon't use import paths from `ember-cli-shims`.\n\nThe import paths in `ember-cli-shims` were never considered public API and\nwere recently replaced by [RFC #176](https://github.com/emberjs/rfcs/pull/176).\nIf you use `ember-cli-babel` with version `6.6.0` or above you can start using\nthe \"New Module Imports\" instead of the old shims or the `Ember` global directly.\nThis will enable us to build better tree shaking feature into Ember CLI.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from 'ember-component';\nimport EmberObject from 'ember-object';\nimport computed from 'ember-computed';\nimport Service from 'ember-service';\nimport inject from 'ember-service/inject';\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport EmberObject, { computed } from '@ember/object';\nimport Service, { inject } from '@ember/service';\n```\n"
  },
  {
    "path": "docs/rules/no-on-calls-in-components.md",
    "content": "# ember/no-on-calls-in-components\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nPrevents using `.on()` in favour of component's lifecycle hooks.\n\nThe order of execution for `on()` is not deterministic.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  abc: on('didInsertElement', function () {\n    /* custom logic */\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  didInsertElement() {\n    /* custom logic */\n  },\n});\n```\n"
  },
  {
    "path": "docs/rules/no-pause-test.md",
    "content": "# ember/no-pause-test\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow use of `pauseTest` helper in tests.\n\nWhen `pauseTest()` is committed and run in CI it can cause runners to hang which is undesirable.\n\n## Rule Details\n\nThis rule aims to prevent `pauseTest()` from being committed and run in CI.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { module, test } from 'qunit';\nimport { setupApplicationTest } from 'ember-qunit';\n\nimport { pauseTest } from '@ember/test-helpers';\n\nmodule('Acceptance | foo test', function (hooks) {\n  setupApplicationTest(hooks);\n\n  test('it hangs', async function () {\n    await this.pauseTest();\n    // or\n    await pauseTest();\n  });\n});\n```\n\n## When Not To Use It\n\nIf you have tests that call `resumeTest()` following a `pauseTest()`\n\n```js\nimport { module, test } from 'qunit';\nimport { setupApplicationTest } from 'ember-qunit';\n\nimport { pauseTest, resumeTest } from '@ember/test-helpers';\n\nmodule('Acceptance | foo test', function (hooks) {\n  setupApplicationTest(hooks);\n\n  test('it runs', function () {\n    const promise = pauseTest();\n\n    // Do some stuff\n\n    resumeTest(); // Done\n  });\n});\n```\n\n## Further Reading\n\n- [ember-test-helpers pauseTest documentation](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#pausetest)\n"
  },
  {
    "path": "docs/rules/no-private-routing-service.md",
    "content": "# ember/no-private-routing-service\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow the use of:\n\n- The private `-routing` service\n- The private `_routerMicrolib` property\n- The private `router:main` property\n\nThere has been a public `router` service since Ember 2.16 and using the private routing service should be unnecessary.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { inject as service } from '@ember/service';\n\nexport default Component.extend({\n  routing: service('-routing'),\n});\n```\n\n```js\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  @service('-routing') routing;\n}\n```\n\n```js\n// When `catchRouterMicrolib` option is enabled.\n\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  @service('router') router;\n\n  get someMethod() {\n    return this.router._routerMicrolib.activeTransition;\n  }\n}\n```\n\n```js\n// When `catchRouterMain` option is enabled.\n\nimport Component from '@ember/component';\nimport { getOwner } from '@ember/application';\n\nexport default class MyComponent extends Component {\n  someFunction() {\n    const router = getOwner(this).lookup('router:main');\n    // ...\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { inject as service } from '@ember/service';\n\nexport default Component.extend({\n  router: service('router'),\n});\n```\n\n```js\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  @service\n  router;\n}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                  | Description                                                                     | Type    | Default |\n| :-------------------- | :------------------------------------------------------------------------------ | :------ | :------ |\n| `catchRouterMain`     | Whether the rule should catch usages of the private property `router:main`.     | Boolean | `true`  |\n| `catchRouterMicrolib` | Whether the rule should catch usages of the private property `_routerMicrolib`. | Boolean | `true`  |\n\n<!-- end auto-generated rule options list -->\n\n## References\n\n[Router RFC](https://github.com/emberjs/rfcs/blob/master/text/0095-router-service.md)\n"
  },
  {
    "path": "docs/rules/no-proxies.md",
    "content": "# ember/no-proxies\n\n<!-- end auto-generated rule header -->\n\nYou may want to disallow the use of Ember proxy objects (`ObjectProxy`, `ArrayProxy`) in your application for a number of reasons:\n\n1. Proxies are relatively rare compared to direct property access on objects.\n2. In part due to their rarity, proxies are not as widely understood.\n3. Proxies can add unnecessary complexity.\n4. Proxies do not support ES5 getters which were introduced in [Ember 3.1](https://blog.emberjs.com/2018/04/13/ember-3-1-released.html) (they still require using `this.get()`)\n\nNote: this rule is not in the `recommended` configuration because there are legitimate usages of proxies.\n\n## Rule Details\n\nThis rule disallows using Ember proxy objects (`ObjectProxy`, `ArrayProxy`).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport ObjectProxy from '@ember/object/proxy';\n```\n\n```js\nimport ArrayProxy from '@ember/array/proxy';\n```\n\n## Related Rules\n\n- [no-get](no-get.md) which may need to be disabled for `this.get()` usages in proxy objects\n\n## References\n\n- [ObjectProxy](https://api.emberjs.com/ember/release/classes/ObjectProxy) spec\n- [ArrayProxy](https://api.emberjs.com/ember/release/classes/ArrayProxy) spec\n"
  },
  {
    "path": "docs/rules/no-replace-test-comments.md",
    "content": "# ember/no-replace-test-comments\n\n<!-- end auto-generated rule header -->\n\nEmber developers using blueprints to generate classes should write real tests.\n\nLeaving the default test comment in place is a sign of a rushed class.\n\nNote: this rule will not be added to the `recommended` configuration because it would cause the default ember-cli blueprint to contain lint violations.\n\n## Rule Details\n\nThis rule aims to nudge developers into writing more/better tests.\n\nIt aims to do this by complaining at them early about a default test file.\n\nThis rule only fires for test files (ending in '-test.js' or '-test.ts')\n\nThis will especially push TDD (test-driven development) on repos with githooks that enforce lint early.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// Replace this with your real tests.\ntest('it exists', function (assert) {\n  const service = this.owner.lookup('service:company');\n  assert.ok(service);\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\ntest('it has a purpose beyond mere existence', function (assert) {\n  const service = this.owner.lookup('service:company');\n  set(service, 'company', myCompanyMock);\n  assert.equal(service.isOnboardingComplete, true, 'the computed property works as expected');\n});\n```\n\n## Migration\n\n- The [testing guides](https://guides.emberjs.com/release/testing/testing-components/) shall be your guide\n- Consider a code coverage tool like [ember-cli-code-coverage](https://github.com/kategengler/ember-cli-code-coverage) as well\n\n## References\n\n- [Learn Test Driven Development in Ember](https://learntdd.in/ember/)\n"
  },
  {
    "path": "docs/rules/no-restricted-property-modifications.md",
    "content": "# ember/no-restricted-property-modifications\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThere are some properties, especially globally-injected ones, that you may want to treat as read-only, and ensure that no one modifies them.\n\n## Rule Details\n\nThis rule prevents modifying the specified properties.\n\nIt also disallows using computed property macros like `alias` and `reads` that enable the specified properties to be indirectly modified.\n\n## Examples\n\nAll examples assume a configuration of `properties: ['currentUser']`.\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { alias, reads } from '@ember/object/computed';\n\nexport default class MyComponent extends Component {\n  @alias('currentUser') aliasForCurrentUser1; // Not allowed\n  @reads('currentUser') aliasForCurrentUser2; // Not allowed\n\n  @alias('currentUser.somePermission1') somePermission1; // Not allowed\n  @reads('currentUser.somePermission2') somePermission2; // Not allowed\n\n  myFunction() {\n    this.set('currentUser', {}); // Not allowed\n    this.set('currentUser.somePermission', true); // Not allowed\n\n    this.currentUser = {}; // Not allowed\n    this.currentUser.somePermission = true; // Not allowed\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { readOnly } from '@ember/object/computed';\n\nexport default class MyComponent extends Component {\n  @readOnly('currentUser.somePermission') somePermission; // Allowed\n\n  myFunction() {\n    console.log(this.currentUser.somePermission); // Allowed\n  }\n}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name         | Description                                                                                                                    | Type     | Required |\n| :----------- | :----------------------------------------------------------------------------------------------------------------------------- | :------- | :------- |\n| `properties` | Array of names of properties that should not be modified (modifying child/nested/sub-properties of these is also not allowed). | String[] | Yes      |\n\n<!-- end auto-generated rule options list -->\n\nNot yet implemented: There is currently no way to configure whether sub-properties are restricted from modification. To make this configurable, the `properties` array option could be updated to also accept objects of the form `{ name: 'myPropertyName', includeSubProperties: false }` where `includeSubProperties` defaults to `true`.\n"
  },
  {
    "path": "docs/rules/no-restricted-resolver-tests.md",
    "content": "# ember/no-restricted-resolver-tests\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDon't use constructs or configuration that use the restricted resolver in tests.\n\n[RFC-0229](https://github.com/emberjs/rfcs/blob/master/text/0229-deprecate-testing-restricted-resolver.md)\nproposed to remove the concept of artificially restricting the resolver used under testing. This rule helps\nidentify anti-patterns in tests that we want to migrate off.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\nIf `integration: true` is not included in the specified options for the APIs listed below. This specifically includes specifying `unit: true`, `needs: []`, or specifying none of the \"test type options\" (`unit`, `needs`,or `integration` options) to the following ember-qunit and ember-mocha API's:\n\n```js\n// ember-qunit\n\nmoduleFor('service:session');\nmoduleFor('service:session', {\n  unit: true,\n});\nmoduleFor('service:session', {\n  needs: ['type:thing'],\n});\nmoduleFor('service:session', 'arg2', ['etc'], {});\n\nmoduleForComponent('display-page');\nmoduleForComponent('display-page', {\n  unit: true,\n});\nmoduleForComponent('display-page', {\n  needs: ['type:thing'],\n});\nmoduleForComponent('display-page', 'arg2', ['etc'], {});\n\nmoduleForModel('post');\nmoduleForModel('post', {\n  unit: true,\n});\nmoduleForModel('post', {\n  needs: ['type:thing'],\n});\nmoduleForModel('post', 'arg2', ['etc'], {});\n```\n\n```js\n// ember-mocha\n\nsetupTest('service:session');\nsetupTest('service:session', {\n  unit: true,\n});\nsetupTest('service:session', {\n  needs: ['type:thing'],\n});\nmoduleFor('arg1', 'arg2', ['etc'], {});\n\nsetupComponentTest('display-page');\nsetupComponentTest('display-page', {\n  unit: true,\n});\nsetupComponentTest('display-page', {\n  needs: ['type:thing'],\n});\nsetupComponentTest('display-page', 'arg2', ['etc'], {});\n\nsetupModelTest('post');\nsetupModelTest('post', {\n  unit: true,\n});\nsetupModelTest('post', {\n  needs: ['type:thing'],\n});\nsetupModelTest('post', 'arg2', ['etc'], {});\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// ember-qunit\n\nmoduleFor('service:session', {\n  integration: true,\n});\n\nmoduleForComponent('display-page', {\n  integration: true,\n});\n\nmoduleFor('service:session', {\n  integration: true,\n});\n```\n\n```js\n// ember-mocha\n\nsetupTest('service:session', {\n  integration: true,\n});\n\nsetupComponentTest('display-page', {\n  integration: true,\n});\n\nsetupModelTest('post', {\n  integration: true,\n});\n```\n\n## Further Reading\n\nIf there are other links that describe the issue this rule addresses, please include them here in a bulleted list.\n"
  },
  {
    "path": "docs/rules/no-restricted-service-injections.md",
    "content": "# ember/no-restricted-service-injections\n\n<!-- end auto-generated rule header -->\n\nIn some parts of your application, you may prefer to disallow certain services from being injected. This can be useful for:\n\n- Deprecating services one folder at a time\n- Creating isolation between different parts of your application\n\n## Rule Details\n\nThis rule disallows injecting specified services under specified paths.\n\n## Examples\n\nWith this example configuration:\n\n```json\n[\n  \"error\",\n  {\n    \"paths\": [\"folder1\", \"folder2\", \"folder3\"],\n    \"services\": [\"deprecated-service\"],\n    \"message\": \"Please stop using this service as it is in the process of being deprecated\"\n  },\n  {\n    \"paths\": [\"isolated-folder\"],\n    \"services\": [\"service-disallowed-for-use-in-isolated-folder\"]\n  },\n  {\n    \"services\": [\"service-disallowed-anywhere\"]\n  }\n]\n```\n\nThis would be disallowed:\n\n```js\n// folder1/my-component.js\n\nclass MyComponent extends Component {\n  @service deprecatedService;\n}\n```\n\n## Configuration\n\nAccepts an array of the objects with the following options:\n\n<!-- begin auto-generated rule options list -->\n\n| Name       | Description                                                                                                                                                                                                                                                                                                    | Type     | Required |\n| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- |\n| `message`  | Optional custom error message to display for violations.                                                                                                                                                                                                                                                       | String   |          |\n| `paths`    | Optional list of regexp file paths that injecting the specified services should be disallowed under (omit this field to match any path) (for glob patterns, use [ESLint `overrides`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns) instead). | String[] |          |\n| `services` | List of (kebab-case) service names that should be disallowed from being injected under the specified paths.                                                                                                                                                                                                    | String[] | Yes      |\n\n<!-- end auto-generated rule options list -->\n\n## Related Rules\n\n- The [no-restricted-imports](https://eslint.org/docs/rules/no-restricted-imports) or [import/no-restricted-paths](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-restricted-paths.md) rules are the JavaScript import statement equivalent of this rule.\n- ember-template-lint has a [no-restricted-invocations](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-restricted-invocations.md) rule for disallowing component usages.\n"
  },
  {
    "path": "docs/rules/no-runloop.md",
    "content": "# ember/no-runloop\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nEmber's runloop functions are not lifecycle-aware and don't ensure that an object's async is cleaned up. It is recommended to use [`ember-lifeline`](https://ember-lifeline.github.io/ember-lifeline/), [`ember-concurrency`](http://ember-concurrency.com/docs/introduction/), or [`@ember/destroyable`](https://rfcs.emberjs.com/id/0580-destroyables/) instead.\n\n## Rule Details\n\nThis rule disallows usage of `@ember/runloop` functions.\n\n## Examples\n\nExample of **incorrect** code for this rule:\n\n```js\nimport Component from '@glimmer/component';\nimport { run } from '@ember/runloop';\n\nexport default class MyComponent extends Component {\n  constructor(...args) {\n    super(...args);\n\n    run.later(() => {\n      this.set('date', new Date());\n    }, 500);\n  }\n}\n```\n\nExample of **correct** code for this rule using `ember-lifeline`:\n\n```js\nimport Component from '@glimmer/component';\nimport { runTask, runDisposables } from 'ember-lifeline';\n\nexport default class MyComponent extends Component {\n  constructor(...args) {\n    super(...args);\n\n    runTask(\n      this,\n      () => {\n        this.set('date', new Date());\n      },\n      500\n    );\n  }\n\n  willDestroy(...args) {\n    super.willDestroy(...args);\n\n    runDisposables(this);\n  }\n}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name        | Description                                                                                                                                                                                                                                                               | Type     |\n| :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------- |\n| `allowList` | If you have `@ember/runloop` functions that you wish to allow, you can configure this rule to allow specific methods. The configuration takes an object with the `allowList` property, which is an array of strings where the strings must be names of runloop functions. | String[] |\n\n<!-- end auto-generated rule options list -->\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/no-runloop': [\n      'error',\n      {\n        allowList: ['debounce', 'begin', 'end'],\n      },\n    ],\n  },\n};\n```\n\n## References\n\n- [require-lifeline](https://github.com/ember-best-practices/eslint-plugin-ember-best-practices/blob/master/guides/rules/require-ember-lifeline.md) - a rule that was originally implemented in eslint-plugin-ember-best-practices\n"
  },
  {
    "path": "docs/rules/no-settled-after-test-helper.md",
    "content": "# ember/no-settled-after-test-helper\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nMost of the test helper functions in\n[`@ember/test-helpers`](https://github.com/emberjs/ember-test-helpers) call\n`settled()` internally, which causes the test to wait until any effects of e.g.\nthe `click()` operation have settled. This means calling `await settled()` after\ncalling one of these test helpers is redundant and should not be necessary.\n\nIn some cases this pattern is mistakenly used to \"wait a little more\", which\nusually indicated a deeper root cause issue, like a race condition or missing\ntest waiter somewhere. This pattern only works sometimes because it causes the\ntest to continue on the next tick of the JS runloop, instead of continuing\ndirectly. It is highly recommended to fix the root cause in these cases instead\nof relying on such brittle workarounds.\n\n## Rule Details\n\nThis rule warns about cases where `await settled()` is used right after a call\nto a test helper function that already calls `settled()` internally (or\n`settled()` itself).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\ntest('...', async function (assert) {\n  await click('.foo');\n  await settled();\n});\n```\n\n```js\ntest('...', async function (assert) {\n  await fillIn('.foo');\n  await settled();\n});\n```\n\n```js\ntest('...', async function (assert) {\n  await settled();\n  await settled();\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\ntest('...', async function (assert) {\n  await waitFor('.foo');\n  await settled();\n});\n```\n\n## Migration\n\n- Have a look at the [ember-test-waiters](https://github.com/emberjs/ember-test-waiters) project\n\n## References\n\n- [`settled()`](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#settled)\n"
  },
  {
    "path": "docs/rules/no-shadow-route-definition.md",
    "content": "# ember/no-shadow-route-definition\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nEnforce no route path definition shadowing in Router.\n\n## Rule Details\n\nThis rule disallows defining shadowing route definitions. Shadowing will result in the router failing to resolve the path of the shadowed route, leading to undesirable and incomprehensible behavior (e.g. hooks of the shadowed route not firing even though the URL matches its path).\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nthis.route('main', { path: '/' }, function () {\n  this.route('nested');\n});\nthis.route('nested');\n```\n\nIn this example from Router perspective both `nested` routes are on URL `/nested`.\n"
  },
  {
    "path": "docs/rules/no-side-effects.md",
    "content": "# ember/no-side-effects\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nWhen using computed properties, do not introduce side effects. Side effects make it much more difficult to reason about the origin of changes.\n\nThis rule currently disallows the following side-effect-causing statements inside computed properties:\n\n- `this.set('x', 123);`\n- `this.setProperties({ x: 123 });`\n- `this.x = 123;`\n\nNote that other side effects like network requests should be avoided as well.\n\n## Examples\n\n```js\nimport Component from '@ember/component';\nimport { alias, filterBy } from '@ember/object/computed';\n\nexport default Component.extend({\n  init(...args) {\n    this.users = [\n      { name: 'Foo', age: 15 },\n      { name: 'Bar', age: 16 },\n      { name: 'Baz', age: 15 },\n    ];\n    this._super(...args);\n  },\n\n  // GOOD:\n  fifteenGood: filterBy('users', 'age', 15),\n  fifteenAmountGood: alias('fifteen.length'),\n\n  // BAD:\n  fifteenAmountBad: 0,\n  fifteenBad: computed('users', function () {\n    const fifteen = this.users.filterBy('items', 'age', 15);\n    this.set('fifteenAmountBad', fifteen.length); // SIDE EFFECT!\n    return fifteen;\n  }),\n});\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                | Description                                                                                    | Type    | Default |\n| :------------------ | :--------------------------------------------------------------------------------------------- | :------ | :------ |\n| `catchEvents`       | Whether the rule should catch function calls that send actions or events.                      | Boolean | `true`  |\n| `checkPlainGetters` | Whether the rule should check plain (non-computed) getters in native classes for side effects. | Boolean | `true`  |\n\n<!-- end auto-generated rule options list -->\n"
  },
  {
    "path": "docs/rules/no-string-prototype-extensions.md",
    "content": "# ember/no-string-prototype-extensions\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nBy default, Ember extends certain native JavaScript objects with additional methods. This can lead to problems in some situations. One example is relying on these methods in an addon that is used inside an app that has the extensions disabled.\n\nThe prototype extensions for the `String` object were deprecated in [RFC #236](https://rfcs.emberjs.com/id/0236-deprecation-ember-string/).\n\n## Rule Details\n\nThis rule will disallow method calls that match any of the forbidden `String` prototype extension method names.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n'myString'.dasherize();\n```\n\n```js\nsomeString.capitalize();\n```\n\n```js\n'<b>foo</b>'.htmlSafe();\n```\n\nExamples of **correct** code for this rule:\n\n```js\ndasherize('myString');\n```\n\n```js\ncapitalize(someString);\n```\n\n```js\nhtmlSafe('<b>foo</b>');\n```\n\n## Migration\n\nReplace e.g.:\n\n```js\n'myString'.dasherize();\n```\n\nwith:\n\n```js\nimport { dasherize } from '@ember/string';\n\ndasherize('myString');\n```\n\n## References\n\n- [Ember prototype extensions documentation](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/)\n- [Ember String prototype extensions deprecation RFC](https://rfcs.emberjs.com/id/0236-deprecation-ember-string/)\n\n## Related Rules\n\n- [no-array-prototype-extensions](no-array-prototype-extensions.md)\n- [no-function-prototype-extensions](no-function-prototype-extensions.md)\n"
  },
  {
    "path": "docs/rules/no-test-and-then.md",
    "content": "# ember/no-test-and-then\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUse `await` instead of `andThen` test wait helper.\n\nIt's no longer necessary to use the `andThen` test wait helper now that the cleaner [async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) syntax is available.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\ntest('behaves correctly', function (assert) {\n  click('.button');\n  andThen(() => {\n    assert.ok(this.myAction.calledOnce);\n  });\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\ntest('behaves correctly', async function (assert) {\n  await click('.button');\n  assert.ok(this.myAction.calledOnce);\n});\n```\n\n## Migration\n\n- [async-await-codemod](https://github.com/sgilroy/async-await-codemod) can help convert async function calls / promise chains to use `await`\n- [ember-test-helpers-codemod](https://github.com/simonihmig/ember-test-helpers-codemod) has transforms such as [click](https://github.com/simonihmig/ember-test-helpers-codemod/blob/master/transforms/acceptance/transforms/click.js) that can be modified to call `makeAwait()` and `dropAndThen()` on the function calls that you're trying to bring into compliance with this rule\n"
  },
  {
    "path": "docs/rules/no-test-import-export.md",
    "content": "# ember/no-test-import-export\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nNo importing of test files.\n\n**TL;DR** Do not import from a test file (a file ending in \"-test.js\") in another test file. Doing so will cause the module and tests from the imported file to be executed again. Similarly, test files should not have any exports.\n\nDue to how qunit unloads a test module, importing a test file will cause any modules and tests within the file to be executed every time it gets loaded. If you want to import any helper functions or tests to be shared between multiple test files, please make it a test-helper that gets imported by the test file.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport setupModule from './some-other-test';\nimport { module, test } from 'qunit';\n\nmodule('Acceptance | module', setupModule());\n```\n\n```js\nimport { beforeEachSetup, testMethod } from './some-other-test';\nimport { module, test } from 'qunit';\n\nmodule('Acceptance | module', beforeEachSetup());\n```\n\n```js\nimport testModule from '../../test-dir/another-test';\nimport { module, test } from 'qunit';\n\nmodule('Acceptance | module', testModule());\n```\n\n```js\n// some-test.js\nexport function beforeEachSetup() {}\n```\n\n```js\n// some-test.js\nfunction beforeEachSetup() {}\n\nexport default { beforeEachSetup };\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport setupModule from './some-test-helper';\nimport { module, test } from 'qunit';\n\nmodule('Acceptance | module', setupModule());\n```\n\n```js\n// some-test-helper.js\nexport function beforeEachSetup() {\n  // ...\n}\n```\n\n```js\n// some-test-helper.js\nfunction beforeEachSetup() {}\n\nexport default { beforeEachSetup };\n```\n\n```js\n// Any imports from `tests/helpers` are allowed.\nimport { setupApplicationTest } from 'tests/helpers/setup-application-test';\n```\n\n```js\n// Any exports from `tests/helpers` are allowed.\n// tests/helpers/setup-application-test.js\nexport default function setupApplicationTest() {}\n```\n"
  },
  {
    "path": "docs/rules/no-test-module-for.md",
    "content": "# ember/no-test-module-for\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUse `module` instead of `moduleFor`.\n\n`moduleForComponent`, `moduleFor`, `moduleForAcceptance`, etc have been deprecated and there are codemods to help migrate.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { moduleFor } from 'ember-qunit';\n\nmoduleFor('Test Name');\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { module } from 'qunit';\n\nmodule('Test Name', function (hooks) {\n  // ...\n});\n```\n\n## Migration\n\nA short guide for how each of the legacy APIs converts to the new APIs:\n\n- `moduleFor`, `moduleForModel`\n\n  ```js\n  import { module, test } from 'qunit';\n  import { setupTest } from 'ember-qunit';\n\n  module('...', function (hooks) {\n    setupTest(hooks);\n  });\n  ```\n\n- `moduleForComponent`\n\n  ```js\n  import { module, test } from 'qunit';\n  import { setupRenderingTest } from 'ember-qunit';\n\n  module('...', function (hooks) {\n    setupRenderingTest(hooks);\n  });\n  ```\n\n- `moduleForAcceptance`\n\n  ```js\n  import { module, test } from 'qunit';\n  import { setupApplicationTest } from 'ember-qunit';\n\n  module('...', function (hooks) {\n    setupApplicationTest(hooks);\n  });\n  ```\n\n## References\n\n- [moduleFor\\* deprecation notice from ember-qunit 4.5.0](https://github.com/emberjs/ember-qunit/blob/master/CHANGELOG.md#rocket-enhancement-1)\n\n- Codemod for automated upgrade of tests: [ember-qunit-codemod](https://github.com/ember-codemods/ember-qunit-codemod)\n"
  },
  {
    "path": "docs/rules/no-test-support-import.md",
    "content": "# ember/no-test-support-import\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nNo importing of test support files into non-test code..\n\n**TL;DR** Do not import from a file located in addon-test-support into non-test code. Doing so will result in production errors that are not capable of being caught in tests as require statements are available in tests but not on production builds.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n> app/routes/index.js\n\n```js\nimport doSomething from '../test-support/some-other-test';\n\nimport Route from '@ember/routing/route';\nimport { action } from '@ember/object';\n\nexport default class SomeRouteRoute extends Route {\n  // …\n  model() {\n    return doSomething();\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n> tests/unit/foo-test.js\n\n```js\nimport setupModule from '../test-support/setup-module';\nimport { module, test } from 'qunit';\n\nmodule('Acceptance | module', setupModule());\n```\n\n> addon-test-support/setupApplication.js\n\n```js\nimport setupModule from '../test-support/setup-module';\n\nexport default function setupApplicationTest(hooks) {\n  setupModule(hooks);\n  // ...\n}\n```\n\nThis is meant as an addition to the [no-test-import-export](no-test-import-export.md) rule as these files do represent test files but are located in addon-test-support rather than in `/tests/`.\n\n## Related Rules\n\n- [no-test-import-export](no-test-import-export.md)\n"
  },
  {
    "path": "docs/rules/no-test-this-render.md",
    "content": "# ember/no-test-this-render\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nEmber's `this.render`/`this.clearRender` method and [`@ember/test-helpers`](https://github.com/emberjs/ember-test-helpers)'s `render`/`clearRender` method are equivalent, but using `@ember/test-helpers`' `render`/`clearRender` method is the recommended approach.\n\n## Rule Details\n\nThe rule invites users to call `@ember/test-helpers`' `render`/`clearRender` method instead of `this.render`/`this.clearRender` in tests.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\ntest('baz', function (assert) {\n  this.render();\n});\n```\n\n```js\ntest('baz', function (assert) {\n  this.clearRender();\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { render } from '@ember/test-helpers';\n\ntest('baz', function (assert) {\n  render();\n});\n```\n\n```js\nimport { clearRender } from '@ember/test-helpers';\n\ntest('baz', function (assert) {\n  clearRender();\n});\n```\n\n## References\n\n- [Rendering Helpers on `@ember/test-helpers`](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#rendering-helpers).\n"
  },
  {
    "path": "docs/rules/no-tracked-built-ins.md",
    "content": "# ember/no-tracked-built-ins\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEnforce usage of `@ember/reactive/collections` imports instead of `tracked-built-ins`.\n\n## Context\n\nPer [RFC #1068](https://github.com/emberjs/rfcs/pull/1068), the tracked collection utilities from the `tracked-built-ins` package are being moved into the framework as `@ember/reactive/collections`. The new API also changes from class constructors (`new TrackedArray(...)`) to factory functions (`trackedArray(...)`).\n\n## Rule Details\n\nThis rule detects imports from `tracked-built-ins` and provides an autofix to convert them to `@ember/reactive/collections` with the new function-based API.\n\nThe following mappings are applied:\n\n| Old (`tracked-built-ins`) | New (`@ember/reactive/collections`) |\n| ------------------------- | ----------------------------------- |\n| `TrackedArray`            | `trackedArray`                      |\n| `TrackedObject`           | `trackedObject`                     |\n| `TrackedMap`              | `trackedMap`                        |\n| `TrackedSet`              | `trackedSet`                        |\n| `TrackedWeakMap`          | `trackedWeakMap`                    |\n| `TrackedWeakSet`          | `trackedWeakSet`                    |\n\nAdditionally, `new` expressions using these imports are automatically converted to direct function calls.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { TrackedArray } from 'tracked-built-ins';\n\nconst arr = new TrackedArray([1, 2, 3]);\n```\n\n```js\nimport { TrackedObject, TrackedMap } from 'tracked-built-ins';\n\nconst obj = new TrackedObject({ a: 1 });\nconst map = new TrackedMap();\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { trackedArray } from '@ember/reactive/collections';\n\nconst arr = trackedArray([1, 2, 3]);\n```\n\n```js\nimport { trackedObject, trackedMap } from '@ember/reactive/collections';\n\nconst obj = trackedObject({ a: 1 });\nconst map = trackedMap();\n```\n\n## Migration\n\nThis rule provides automatic fixes via `--fix`. Running ESLint with the `--fix` flag will:\n\n1. Replace `import { TrackedArray } from 'tracked-built-ins'` with `import { trackedArray } from '@ember/reactive/collections'`\n2. Replace `new TrackedArray(...)` with `trackedArray(...)`\n\n## References\n\n- [RFC #1068: Built in tracking utilities for common collections](https://github.com/emberjs/rfcs/pull/1068)\n- [`tracked-built-ins` package](https://github.com/tracked-tools/tracked-built-ins)\n"
  },
  {
    "path": "docs/rules/no-tracked-properties-from-args.md",
    "content": "# ember/no-tracked-properties-from-args\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow creation of @tracked properties from args.\n\n## Rule Details\n\nThis rule disallows the creation of @tracked properties with values from `this.args`. The @tracked property will not be updated when the args change, which is almost never what you want. Instead, use a getter to derive the desired state.\nIf you need to modify a specific arg, consider having the parent provide a way for the child component to update it. This avoids having two sources of truth that you will need to keep in sync.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nclass Example {\n  @tracked someValue = this.args.someValue;\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nclass Example {\n  get someValue() {\n    return this.args.someValue;\n  }\n}\n```\n\n## References\n\n- [Glimmer Components - args](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/#toc_getting-used-to-glimmer-components) guide\n- [tracked](https://guides.emberjs.com/release/in-depth-topics/autotracking-in-depth/) guide\n"
  },
  {
    "path": "docs/rules/no-try-invoke.md",
    "content": "# ember/no-try-invoke\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule will catch and prevent the use of `tryInvoke`.\n\n## Rule Details\n\nThis rule aims to disallow the usage of `tryInvoke`. Native JavaScript language now supports [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) and developers are encouraged to use optional chaining `?.()` instead.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { tryInvoke } from '@ember/utils';\n\nclass FooComponent extends Component {\n  foo() {\n    tryInvoke(this.args, 'bar', ['baz']);\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nclass FooComponent extends Component {\n  foo() {\n    this.args.bar?.('baz');\n  }\n}\n```\n\n## References\n\n- [RFC](https://github.com/emberjs/rfcs/pull/673) to deprecate `tryInvoke`\n- [spec](https://api.emberjs.com/ember/release/functions/@ember%2Futils/tryInvoke)\n"
  },
  {
    "path": "docs/rules/no-unnecessary-index-route.md",
    "content": "# ember/no-unnecessary-index-route\n\n<!-- end auto-generated rule header -->\n\nDisallow unnecessary `index` route definition.\n\nThe `index` route (for the `/` path) is automatically provided at every level of nesting and does not need to be defined in the router.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nthis.route('index');\n```\n\n```js\nthis.route('index', { path: '/' });\n```\n\nExamples of **correct** code for this rule:\n\n```js\nthis.route('blog-posts');\n```\n\n## References\n\n- [Ember Routing Guide](https://guides.emberjs.com/release/routing/)\n\n## Related Rules\n\n- [no-capital-letters-in-routes](no-capital-letters-in-routes.md)\n- [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md)\n- [routes-segments-snake-case](routes-segments-snake-case.md)\n"
  },
  {
    "path": "docs/rules/no-unnecessary-route-path-option.md",
    "content": "# ember/no-unnecessary-route-path-option\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow unnecessary route `path` option.\n\nWhen defining a route, it's not necessary to specify the `path` option if it matches the route name.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nthis.route('blog-posts', { path: '/blog-posts' });\n```\n\nExamples of **correct** code for this rule:\n\n```js\nthis.route('blog-posts');\n```\n\n```js\nthis.route('blog-posts', { path: '/blog' });\n```\n\n## References\n\n- [Ember Routing Guide](https://guides.emberjs.com/release/routing/)\n\n## Related Rules\n\n- [no-capital-letters-in-routes](no-capital-letters-in-routes.md)\n- [no-unnecessary-index-route](no-unnecessary-index-route.md)\n- [route-path-style](route-path-style.md)\n- [routes-segments-snake-case](routes-segments-snake-case.md)\n"
  },
  {
    "path": "docs/rules/no-unnecessary-service-injection-argument.md",
    "content": "# ember/no-unnecessary-service-injection-argument\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow unnecessary argument when injecting service.\n\nIt's not necessary to specify an injected service's name as an argument when the property name matches the service name.\n\nNote: this rule is not in the `recommended` configuration because this is more of a stylistic preference and some developers may prefer to use the explicit service injection argument to avoid potentially costly lookup/normalization of the service name.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { inject as service } from '@ember/service';\n\nexport default Component.extend({\n  myServiceName: service('myServiceName'),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  myServiceName: service(),\n});\n```\n\n```js\nexport default Component.extend({\n  myServiceName: service('my-service-name'),\n});\n```\n\n```js\nexport default Component.extend({\n  otherSpecialName: service('my-service-name'),\n});\n```\n\n## Related Rules\n\n- [no-implicit-service-injection-argument](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-implicit-service-injection-argument.md) is the opposite of this rule\n\n## References\n\n- Ember [Services](https://guides.emberjs.com/release/applications/services/) guide\n- Ember [inject](https://api.emberjs.com/ember/release/functions/@ember%2Fservice/inject) function spec\n"
  },
  {
    "path": "docs/rules/no-unused-services.md",
    "content": "# ember/no-unused-services\n\n💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n<!-- end auto-generated rule header -->\n\nDisallow unused service injections.\n\nBy removing unused service injections, we can reduce the amount of code we have and improve code readability.\n\n**Warning**: This rule can exhibit false positives when an injected service is only used in:\n\n- The corresponding handlebars template file for a controller or component\n- A mixin or parent class that the current class extends from\n- A child class that extends from the current class\n\nGiven these significant limitations, the rule is not currently recommended for production usage, but some may find it useful to experiment with. The rule will not be added to the `recommended` configuration unless the limitations can be addressed.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@glimmer/component';\n\nexport default class MyComponent extends Component {\n  @service() myService;\n\n  // myService is not referenced below at all\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@glimmer/component';\n\nexport default class MyComponent extends Component {\n  @service() myService;\n\n  get someProperty() {\n    return this.myService.getSomething(); // using the injected service\n  }\n}\n```\n\n## References\n\n- Ember [Services](https://guides.emberjs.com/release/applications/services/) guide\n- Ember [inject](https://api.emberjs.com/ember/release/functions/@ember%2Fservice/inject) function spec\n"
  },
  {
    "path": "docs/rules/no-volatile-computed-properties.md",
    "content": "# ember/no-volatile-computed-properties\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nVolatile computed properties are deprecated as of Ember 3.9.\n\n## Rule Details\n\nThis rule disallows using volatile computed properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nconst Person = EmberObject.extend({\n  fullName: computed(function () {\n    return `${this.firstName} ${this.lastName}`;\n  }).volatile(),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nconst Person = EmberObject.extend({\n  // Native getter:\n  get fullName() {\n    return `${this.firstName} ${this.lastName}`;\n  },\n});\n```\n\n## References\n\n- [Deprecation RFC](https://github.com/emberjs/rfcs/blob/master/text/0370-deprecate-computed-volatile.md)\n- [Deprecation list](https://deprecations.emberjs.com/v3.x/#toc_computed-property-volatile)\n- [Volatile spec](https://api.emberjs.com/ember/release/classes/ComputedProperty/methods/volatile?anchor=volatile)\n- [Computed property spec](https://api.emberjs.com/ember/release/classes/ComputedProperty)\n"
  },
  {
    "path": "docs/rules/order-in-components.md",
    "content": "# ember/order-in-components\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nNote: this rule will not be added to the `recommended` configuration because it enforces an opinionated, stylistic preference.\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name    | Type  |\n| :------ | :---- |\n| `order` | Array |\n\n<!-- end auto-generated rule options list -->\n\n```js\nconst rules = {\n  'ember/order-in-components': [\n    2,\n    {\n      order: [\n        'spread',\n        'service',\n        'property',\n        'empty-method',\n        'single-line-function',\n        'multi-line-function',\n        'observer',\n        'init',\n        'didReceiveAttrs',\n        'willRender',\n        'willInsertElement',\n        'didInsertElement',\n        'didRender',\n        'didUpdateAttrs',\n        'willUpdate',\n        'didUpdate',\n        'willDestroyElement',\n        'willClearRender',\n        'didDestroyElement',\n        'actions',\n        'method',\n      ],\n    },\n  ],\n};\n```\n\nIf you want some of properties to be treated equally in order you can group them into arrays, like so:\n\n```js\norder: [\n  'service',\n  'property',\n  ['single-line-function', 'multi-line-function'],\n  'observer',\n  'init',\n  'didReceiveAttrs',\n  'willRender',\n  'willInsertElement',\n  'didInsertElement',\n  'didRender',\n  'didUpdateAttrs',\n  'willUpdate',\n  'didUpdate',\n  'willDestroyElement',\n  'willClearRender',\n  'didDestroyElement',\n  'actions',\n  ['method', 'empty-method'],\n];\n```\n\n### Custom Properties\n\nIf you would like to specify ordering for a property type that is not listed, you can use the custom property syntax `custom:myPropertyName` in the order list to specify where the property should go.\n\n### Additional Properties\n\nYou can find the full list of properties [in property-order.js](../../lib/utils/property-order.js#L10).\n\n## Description\n\nYou should write code grouped and ordered in this way:\n\n1. Services\n2. Default values\n3. Single line computed properties\n4. Multiline computed properties\n5. Observers\n6. Lifecycle Hooks (in execution order)\n7. Actions\n8. Custom / private methods\n\n## Examples\n\n```js\nconst {\n  Component,\n  computed,\n  inject: { service },\n} = Ember;\nconst { alias } = computed;\n\nexport default Component.extend({\n  // 1. Services\n  i18n: service(),\n\n  // 2. Properties\n  role: 'sloth',\n\n  // 3. Empty methods\n  onRoleChange() {},\n\n  // 4. Single line Computed Property\n  vehicle: alias('car'),\n\n  // 5. Multiline Computed Property\n  levelOfHappiness: computed('attitude', 'health', function () {\n    const result = this.attitude * this.health * Math.random();\n    return result;\n  }),\n\n  // 6. Observers\n  onVehicleChange: observer('vehicle', function () {\n    // observer logic\n  }),\n\n  // 7. Lifecycle Hooks\n  init() {\n    // custom init logic\n  },\n\n  didInsertElement() {\n    // custom didInsertElement logic\n  },\n\n  willDestroyElement() {\n    // custom willDestroyElement logic\n  },\n\n  // 8. All actions\n  actions: {\n    sneakyAction() {\n      return this._secretMethod();\n    },\n  },\n\n  // 9. Custom / private methods\n  _secretMethod() {\n    // custom secret method logic\n  },\n});\n```\n\n## Help Wanted\n\n| Issue                                      | Link                                                                |\n| :----------------------------------------- | :------------------------------------------------------------------ |\n| ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) |\n"
  },
  {
    "path": "docs/rules/order-in-controllers.md",
    "content": "# ember/order-in-controllers\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nNote: this rule will not be added to the `recommended` configuration because it enforces an opinionated, stylistic preference.\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name    | Type  |\n| :------ | :---- |\n| `order` | Array |\n\n<!-- end auto-generated rule options list -->\n\n```js\nconst rules = {\n  'ember/order-in-controllers': [\n    2,\n    {\n      order: [\n        'spread',\n        'controller',\n        'service',\n        'query-params',\n        'inherited-property',\n        'property',\n        'single-line-function',\n        'multi-line-function',\n        'observer',\n        'actions',\n        ['method', 'empty-method'],\n      ],\n    },\n  ],\n};\n```\n\nIf you want some of properties to be treated equally in order you can group them into arrays, like so:\n\n```js\norder: [\n  ['controller', 'service', 'query-params'],\n  'inherited-property',\n  'property',\n  ['single-line-function', 'multi-line-function'],\n];\n```\n\n### Custom Properties\n\nIf you would like to specify ordering for a property type that is not listed, you can use the custom property syntax `custom:myPropertyName` in the order list to specify where the property should go.\n\n### Additional Properties\n\nYou can find the full list of properties [in property-order.js](../../lib/utils/property-order.js#L10).\n\n## Description\n\nYou should write code grouped and ordered in this way:\n\n1. Controller injections\n2. Service injections\n3. Query params\n4. Default controller's properties\n5. Custom properties\n6. Single line computed properties\n7. Multi line computed properties\n8. Observers\n9. Actions\n10. Custom / private methods\n\n## Examples\n\n```js\nconst {\n  Controller,\n  computed,\n  inject: { controller, service },\n} = Ember;\n\nexport default Controller.extend({\n  // 1. Controller injections\n  application: controller(),\n\n  // 2. Service injections\n  currentUser: service(),\n\n  // 3. Query params\n  queryParams: ['view'],\n\n  // 4. Default controller's properties\n  concatenatedProperties: ['concatenatedProperty'],\n\n  // 5. Custom properties\n  attitude: 10,\n\n  // 6. Single line Computed Property\n  health: alias('model.health'),\n\n  // 7. Multiline Computed Property\n  levelOfHappiness: computed('attitude', 'health', function () {\n    return this.attitude * this.health * Math.random();\n  }),\n\n  // 8. Observers\n  onVehicleChange: observer('vehicle', function () {\n    // observer logic\n  }),\n\n  // 9. All actions\n  actions: {\n    sneakyAction() {\n      return this._secretMethod();\n    },\n  },\n\n  // 10. Custom / private methods\n  _secretMethod() {\n    // custom secret method logic\n  },\n});\n```\n\nThis rule checks ordering only; it does not enforce indentation or other whitespace formatting.\n"
  },
  {
    "path": "docs/rules/order-in-models.md",
    "content": "# ember/order-in-models\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nNote: this rule will not be added to the `recommended` configuration because it enforces an opinionated, stylistic preference.\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name    | Type  |\n| :------ | :---- |\n| `order` | Array |\n\n<!-- end auto-generated rule options list -->\n\n```js\nconst rules = {\n  'ember/order-in-models': [\n    2,\n    {\n      order: ['spread', 'attribute', 'relationship', 'single-line-function', 'multi-line-function'],\n    },\n  ],\n};\n```\n\nIf you want some of properties to be treated equally in order you can group them into arrays, like so:\n\n```js\norder: ['attribute', 'relationship', ['single-line-function', 'multi-line-function']];\n```\n\n### Custom Properties\n\nIf you would like to specify ordering for a property type that is not listed, you can use the custom property syntax `custom:myPropertyName` in the order list to specify where the property should go.\n\n### Additional Properties\n\nYou can find the full list of properties [in property-order.js](../../lib/utils/property-order.js#L10).\n\n## Description\n\nYou should write code grouped and ordered in this way:\n\n1. Attributes\n2. Relations\n3. Single line computed properties\n4. Multiline computed properties\n5. Other structures (custom methods etc.)\n\nThis rule checks ordering only; it does not enforce indentation or other whitespace formatting.\n\n## Examples\n\n```js\n// GOOD\nexport default Model.extend({\n  // 1. Attributes\n  shape: attr('string'),\n\n  // 2. Relations\n  behaviors: hasMany('behaviour'),\n\n  // 3. Computed Properties\n  mood: computed('health', 'hunger', function () {\n    const result = this.health * this.hunger;\n    return result;\n  }),\n});\n```\n\n```js\n// BAD\nexport default Model.extend({\n  mood: computed('health', 'hunger', function () {\n    const result = this.health * this.hunger;\n    return result;\n  }),\n\n  hat: attr('string'),\n\n  behaviors: hasMany('behaviour'),\n\n  shape: attr('string'),\n});\n```\n"
  },
  {
    "path": "docs/rules/order-in-routes.md",
    "content": "# ember/order-in-routes\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nNote: this rule will not be added to the `recommended` configuration because it enforces an opinionated, stylistic preference.\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name    | Type  |\n| :------ | :---- |\n| `order` | Array |\n\n<!-- end auto-generated rule options list -->\n\n```js\nconst rules = {\n  'ember/order-in-routes': [\n    2,\n    {\n      order: [\n        'spread',\n        'service',\n        'inherited-property',\n        'property',\n        'single-line-function',\n        'multi-line-function',\n        'beforeModel',\n        'model',\n        'afterModel',\n        'serialize',\n        'redirect',\n        'activate',\n        'setupController',\n        'renderTemplate',\n        'resetController',\n        'deactivate',\n        'actions',\n        ['method', 'empty-method'],\n      ],\n    },\n  ],\n};\n```\n\nIf you want some of properties to be treated equally in order you can group them into arrays, like so:\n\n```js\norder: [\n  'service',\n  ['inherited-property', 'property'],\n  'model',\n  [\n    'beforeModel',\n    'model',\n    'afterModel',\n    'serialize',\n    'redirect',\n    'activate',\n    'setupController',\n    'renderTemplate',\n    'resetController',\n    'deactivate',\n  ],\n  'actions',\n  ['method', 'empty-method'],\n];\n```\n\n### Custom Properties\n\nIf you would like to specify ordering for a property type that is not listed, you can use the custom property syntax `custom:myPropertyName` in the order list to specify where the property should go.\n\n### Additional Properties\n\nYou can find the full list of properties [in property-order.js](../../lib/utils/property-order.js#L10).\n\n## Description\n\nYou should write code grouped and ordered in this way:\n\n1. Services\n2. Default route's properties\n3. Custom properties\n4. beforeModel() hook\n5. model() hook\n6. afterModel() hook\n7. Other lifecycle hooks in execution order (serialize, redirect, etc)\n8. Actions\n9. Custom / private methods\n\n## Examples\n\n```js\nconst {\n  Route,\n  inject: { service },\n} = Ember;\n\nexport default Route.extend({\n  // 1. Services\n  currentUser: service(),\n\n  // 2. Default route's properties\n  queryParams: {\n    sortBy: { refreshModel: true },\n  },\n\n  // 3. Custom properties\n  customProp: 'test',\n\n  // 4. beforeModel hook\n  beforeModel() {\n    if (!this.currentUser.isAdmin) {\n      this.transitionTo('index');\n    }\n  },\n\n  // 5. model hook\n  model() {\n    return this.store.findAll('article');\n  },\n\n  // 6. afterModel hook\n  afterModel(articles) {\n    for (const article of articles) {\n      article.set('foo', 'bar');\n    }\n  },\n\n  // 7. Other route's methods\n  setupController(controller) {\n    controller.set('foo', 'bar');\n  },\n\n  // 8. All actions\n  actions: {\n    sneakyAction() {\n      return this._secretMethod();\n    },\n  },\n\n  // 9. Custom / private methods\n  _secretMethod() {\n    // custom secret method logic\n  },\n});\n```\n\nThis rule checks ordering only; it does not enforce indentation or other whitespace formatting.\n"
  },
  {
    "path": "docs/rules/prefer-ember-test-helpers.md",
    "content": "# ember/prefer-ember-test-helpers\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule ensures the correct Ember test helper is imported when using methods that have a native window counterpart.\n\nThere are currently 3 Ember test helper methods that have a native window counterpart:\n\n- blur\n- find\n- focus\n\nIf these methods are not properly imported from Ember's test-helpers suite, and the native window method version is used instead, any intended asynchronous functions won't work as intended, which can cause tests to fail silently.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\ntest('foo', async (assert) => {\n  await blur('.some-element');\n});\n```\n\n```js\ntest('foo', async (assert) => {\n  await find('.some-element');\n});\n```\n\n```js\ntest('foo', async (assert) => {\n  await focus('.some-element');\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { blur } from '@ember/test-helpers';\n\ntest('foo', async (assert) => {\n  await blur('.some-element');\n});\n```\n\n```js\nimport { find } from '@ember/test-helpers';\n\ntest('foo', async (assert) => {\n  await find('.some-element');\n});\n```\n\n```js\nimport { focus } from '@ember/test-helpers';\n\ntest('foo', async (assert) => {\n  await focus('.some-element');\n});\n```\n\n## References\n\n- [Web API Window Methods](https://developer.mozilla.org/en-US/docs/Web/API/Window#Methods)\n- [Ember Test Helpers API Methods](https://github.com/emberjs/ember-test-helpers/blob/master/API.md)\n"
  },
  {
    "path": "docs/rules/require-async-inverse-relationship.md",
    "content": "# ember/require-async-inverse-relationship\n\n<!-- end auto-generated rule header -->\n\nThis rule ensures that the `async` and `inverse` properties are specified in `@belongsTo` and `@hasMany` decorators in Ember Data models.\n\n## Rule Details\n\nThis rule disallows:\n\n- Using `@belongsTo` without specifying the `async` and `inverse` properties.\n- Using `@hasMany` without specifying the `async` and `inverse` properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Model, { belongsTo, hasMany } from '@ember-data/model';\n\nexport default class FolderModel extends Model {\n  @hasMany('folder', { inverse: 'parent' }) children;\n  @belongsTo('folder', { inverse: 'children' }) parent;\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Model, { belongsTo, hasMany } from '@ember-data/model';\n\nexport default class FolderModel extends Model {\n  @hasMany('folder', { async: true, inverse: 'parent' }) children;\n  @belongsTo('folder', { async: true, inverse: 'children' }) parent;\n}\n```\n\n## References\n\n- [Deprecate Non Strict Relationships](https://deprecations.emberjs.com/id/ember-data-deprecate-non-strict-relationships)\n- [Ember Data Relationships](https://guides.emberjs.com/release/models/relationships)\n"
  },
  {
    "path": "docs/rules/require-computed-macros.md",
    "content": "# ember/require-computed-macros\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nIt is preferred to use Ember's computed property macros as opposed to manually writing out logic in a computed property function. Reasons include:\n\n- Conciseness\n- Readability\n- Reduced chance of typos\n- Reduced chance of missing dependencies\n\nNote that by default, this rule only applies in classic classes (i.e. `Component.extend({})`) and not in native JavaScript classes with decorators (read more about the `includeNativeGetters` option in the configuration section of this doc).\n\n## Rule Details\n\nThis rule requires using Ember's computed property macros when possible.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { computed } from '@ember/object';\n\nexport default Component.extend({\n  propReads: computed('x', function () {\n    return this.x;\n  }),\n\n  propAnd: computed('x', 'y', function () {\n    return this.x && this.y;\n  }),\n\n  propOr: computed('x', 'y', function () {\n    return this.x || this.y;\n  }),\n\n  propGt: computed('x', function () {\n    return this.x > 123;\n  }),\n\n  propGte: computed('x', function () {\n    return this.x >= 123;\n  }),\n\n  propLt: computed('x', function () {\n    return this.x < 123;\n  }),\n\n  propLte: computed('x', function () {\n    return this.x <= 123;\n  }),\n\n  propNot: computed('x', function () {\n    return !this.x;\n  }),\n\n  propEqual: computed('x', function () {\n    return this.x === 123;\n  }),\n\n  propFilterBy: computed('chores.@each.done', function () {\n    return this.chores.filterBy('done', true);\n  }),\n\n  propMapBy: computed('children.@each.age', function () {\n    return this.children.mapBy('age');\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport {\n  reads,\n  and,\n  or,\n  gt,\n  gte,\n  lt,\n  lte,\n  not,\n  equal,\n  filterBy,\n  mapBy,\n} from '@ember/object/computed';\n\nexport default Component.extend({\n  propReads: reads('x'),\n\n  propAnd: and('x', 'y'),\n\n  propOr: or('x', 'y'),\n\n  propGt: gt('x', 123),\n\n  propGte: gte('x', 123),\n\n  propLt: lt('x', 123),\n\n  propLte: lte('x', 123),\n\n  propNot: not('x'),\n\n  propEqual: equal('x', 123),\n\n  propFilterBy: filterBy('chores', 'done', true),\n\n  propMapBy: mapBy('children', 'age'),\n});\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                   | Description                                                                                                                                                                                                                                                                                                                              | Type    | Default |\n| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `includeNativeGetters` | Whether the rule should check and autofix computed properties with native getters (i.e. `@computed() get someProp() {}`) to use computed property macros. This is off by default because in the Ember Octane world, the better improvement would be to keep the native getter and use tracked properties instead of computed properties. | Boolean | `false` |\n\n<!-- end auto-generated rule options list -->\n\n## References\n\n- [Guide](https://guides.emberjs.com/release/object-model/computed-properties/) for computed properties\n- [Spec](https://api.emberjs.com/ember/release/modules/@ember%2Fobject#functions-computed) for computed property macros\n\n## Related Rules\n\n- [no-incorrect-computed-macros](no-incorrect-computed-macros.md)\n"
  },
  {
    "path": "docs/rules/require-computed-property-dependencies.md",
    "content": "# ember/require-computed-property-dependencies\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nComputed properties should have their property dependencies listed out so that they can recompute upon changes.\n\n## Rule Details\n\nThis rule requires dependencies to be declared statically in computed properties. Properties accessed within the computed property function that are not listed out are assumed to be missing dependencies. Various forms of accessing properties will be detected including:\n\n- `this.get('property')`\n- `this.getProperties('a', 'b')`\n- `Ember.get(this, 'property')`\n- `this.property` (ES5 getter)\n\nThis rule has an autofixer that will automatically add missing dependencies to computed properties.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\nexport default EmberObject.extend({\n  name: computed(function () {\n    return `${this.firstName} ${this.lastName}`;\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport EmberObject, { computed } from '@ember/object';\n\nexport default EmberObject.extend({\n  name: computed('firstName', 'lastName', function () {\n    return `${this.firstName} ${this.lastName}`;\n  }),\n});\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                  | Description                                                                                                       | Type    | Default |\n| :-------------------- | :---------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `allowDynamicKeys`    | Whether the rule should allow or disallow dynamic (variable / non-string) dependency keys in computed properties. | Boolean | `true`  |\n| `requireServiceNames` | Whether the rule should require injected service names to be listed as dependency keys in computed properties.    | Boolean | `false` |\n\n<!-- end auto-generated rule options list -->\n\n## References\n\n- [Guide](https://guides.emberjs.com/release/object-model/computed-properties/) for computed properties\n"
  },
  {
    "path": "docs/rules/require-fetch-import.md",
    "content": "# ember/require-fetch-import\n\n<!-- end auto-generated rule header -->\n\nUsing `fetch()` without importing it causes the browser to use the native,\nnon-wrapped `window.fetch()`. This is generally fine, but makes testing harder\nbecause this non-wrapped version does not have a built-in test waiter. Because\nof this it is generally better to use [ember-fetch] and explicitly\n`import fetch from 'fetch'`.\n\n## Rule Details\n\nThe rule looks for `fetch()` calls and reports them as issues if no\ncorresponding import declaration is found.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nconst result = fetch('/something');\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport fetch from 'fetch';\n\nconst result = fetch('/something');\n```\n\n## Migration\n\n- Add `import fetch from 'fetch';` to all files that need it\n\n## References\n\n- [@ember/test-waiters](https://github.com/emberjs/ember-test-waiters) addon\n\n[ember-fetch]: https://github.com/ember-cli/ember-fetch/\n"
  },
  {
    "path": "docs/rules/require-return-from-computed.md",
    "content": "# ember/require-return-from-computed\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nAlways return a value from a computed property function.\n\nNote that this rule applies only to computed properties in classic classes (i.e. `Component.extend({})`) and not in native JavaScript classes with decorators. ESLint already has two recommended rules [getter-return] and [no-setter-return] that enforce the correct behavior with native classes.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n/* eslint \"consistent-return\": \"off\" */\nimport Component from '@ember/component';\nimport { computed } from '@ember/object';\n\nexport default Component.extend({\n  firstName: null,\n  lastName: null,\n\n  fullName: computed('firstName', 'lastName', {\n    get() {\n      return `${this.firstName} ${this.lastName}`;\n    },\n    set(key, value) {\n      const [firstName, lastName] = value.split(/\\s+/);\n      this.set('firstName', firstName);\n      this.set('lastName', lastName);\n      // Missing return here.\n    },\n  }),\n\n  salutation: computed('firstName', function () {\n    if (this.firstName) {\n      return `Dr. ${this.firstName}`;\n    }\n    // Missing return here.\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\nimport { computed } from '@ember/object';\n\nexport default Component.extend({\n  firstName: null,\n  lastName: null,\n\n  fullName: computed('firstName', 'lastName', {\n    get() {\n      return `${this.firstName} ${this.lastName}`;\n    },\n    set(key, value) {\n      const [firstName, lastName] = value.split(/\\s+/);\n      this.set('firstName', firstName);\n      this.set('lastName', lastName);\n      return value;\n    },\n  }),\n\n  salutation: computed('firstName', function () {\n    if (this.firstName) {\n      return `Dr. ${this.firstName}`;\n    }\n    return '';\n  }),\n});\n```\n\n## Migration\n\nTo avoid false positives from relying on implicit returns in some code branches, you may want to enforce [consistent-return] alongside this rule.\n\n## Related Rules\n\n- [consistent-return] from eslint\n- [getter-return] from eslint\n- [no-setter-return] from eslint\n\n[consistent-return]: https://eslint.org/docs/rules/consistent-return\n[getter-return]: https://eslint.org/docs/rules/getter-return\n[no-setter-return]: https://eslint.org/docs/rules/no-setter-return\n"
  },
  {
    "path": "docs/rules/require-super-in-lifecycle-hooks.md",
    "content": "# ember/require-super-in-lifecycle-hooks\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nCall super in lifecycle hooks.\n\nWhen overriding lifecycle hooks inside Ember Components, Controllers, Routes, Mixins, or Services, it is necessary to include a call to super.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  init() {\n    this.set('items', []);\n  },\n});\n```\n\n```js\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  didInsertElement() {\n    // ...\n  },\n});\n```\n\n```js\nimport Component from '@ember/component';\n\nclass Foo extends Component {\n  init() {\n    // ...\n  }\n}\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  init(...args) {\n    this._super(...args);\n    this.set('items', []);\n  },\n});\n```\n\n```js\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  didInsertElement(...args) {\n    this._super(...args);\n    // ...\n  },\n});\n```\n\n```js\nimport Component from '@ember/component';\n\nclass Foo extends Component {\n  init(...args) {\n    super.init(...args);\n    // ...\n  }\n}\n```\n\n```js\nimport Component from '@ember/component';\n\nclass Foo extends Component {\n  didInsertElement(...args) {\n    super.didInsertElement(...args);\n    // ...\n  }\n}\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                 | Description                                                                                       | Type    | Default |\n| :------------------- | :------------------------------------------------------------------------------------------------ | :------ | :------ |\n| `checkInitOnly`      | Whether the rule should only check the `init` lifecycle hook and not other lifecycle hooks.       | Boolean | `false` |\n| `checkNativeClasses` | Whether the rule should check lifecycle hooks in native classes (in addition to classic classes). | Boolean | `true`  |\n\n<!-- end auto-generated rule options list -->\n"
  },
  {
    "path": "docs/rules/require-tagless-components.md",
    "content": "# ember/require-tagless-components\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows using the wrapper element of a component.\n\nEmber allows you to disable the wrapper element on a component (turning it into a \"tagless\" component). This is now the preferred style and the _only_ style allowed with Glimmer components.\n\n## Rule Details\n\nInstead of having the wrapper element implicitly defined by the component, all DOM elements should be represented in the component's template.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\n// \"Classic\"-class Ember components that have the default `tagName` of `div`\nimport Component from '@ember/component';\n\nexport default Component.extend({});\n```\n\n```js\n// \"Classic\"-class Ember components that have a `tagName` configured to something besides `''`\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  tagName: 'span',\n});\n```\n\n```js\n// \"Native\"-class Ember components that have the default `tagName` of `div`\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {}\n```\n\n```js\n// \"Native\"-class Ember components that have a `tagName` configured to something besides `''`\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  tagName = 'span';\n}\n```\n\n```js\n// \"Native\"-class Ember components that use the `@tagName` decorator configured to something besides `''`\nimport Component from '@ember/component';\nimport { tagName } from '@ember-decorators/component';\n\n@tagName('span')\nexport default class MyComponent extends Component {}\n```\n\nExamples of **correct** code for this rule:\n\n```js\n// \"Class\"-class Ember components that have a `tagName` configured to `''`\nimport Component from '@ember/component';\n\nexport default Component.extend({\n  tagName: '',\n});\n```\n\n```js\n// \"Native\"-class Ember components that have a `tagName` configured to `''`\nimport Component from '@ember/component';\n\nexport default class MyComponent extends Component {\n  tagName = '';\n}\n```\n\n```js\n// \"Native\"-class Ember components that use the `@tagName` decorator configured `''`\nimport Component from '@ember/component';\nimport { tagName } from '@ember-decorators/component';\n\n@tagName('')\nexport default class MyComponent extends Component {}\n```\n\n```js\n// Glimmer components never have a `tagName` and are always valid\nimport Component from '@glimmer/component';\n\nexport default class MyComponent extends Component {}\n```\n\n## Caveats\n\n- Rule does not catch cases where a Mixin is included to configure the `tagName` property.\n\n## Fixing This Rule\n\nYou can take two approaches to fixing this rule:\n\n1. Convert to a Glimmer component\n2. Set the `tagName` in your Ember component definition to `''` (an empty string)\n\nNote that you might want to wrap the component template in an additional element, if the usage of the component expects the wrapping element to exist. Classes and attributes should be applied to that wrapper element, as well as forwarding any attributes assigned to the component through `...attributes`.\n\n## When Not To Use It\n\n- If you are not prepared to convert your components to be tag-less, wrapping the associated template in a tag and applying `...attributes` where appropriate, you should not enable this rule.\n\n## Further Reading\n\n- [Glimmer Components](https://glimmerjs.com/guides/components-and-actions)\n  - Glimmer components are \"the future\" for Ember, and are _always_ tagless. Using them now, or changing your Ember components to be tagless, helps to modernize your codebase and prepare for a point in the future where components _never_ have a wrapper element.\n- [RFC #311 \"Angle Bracket Invocation\" (HTML Attributes section)](https://emberjs.github.io/rfcs/0311-angle-bracket-invocation.html#html-attributes)\n  - Discusses forwarding attributes on a component to a DOM element using `...attributes`\n"
  },
  {
    "path": "docs/rules/require-valid-css-selector-in-test-helpers.md",
    "content": "# ember/require-valid-css-selector-in-test-helpers\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nTest helpers and querySelector methods should be called with valid CSS selectors. Most of the time invalid selectors will result in a failing test but that is not always the case.\n\nOne example of invalid CSS selectors which do not cause failing tests is when using unclosed attribute selector blocks. Tests happen to pass today in this case due to a quirk in the CSS selector spec which is marked as [WontFix by Chromium](https://bugs.chromium.org/p/chromium/issues/detail?id=460399#c6).\n\n## Rule Details\n\nThis rule requires the use of valid CSS selectors in test helpers and querySelector methods.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport { test } from 'qunit';\n\ntest('foo', function (assert) {\n  assert.dom('[data-test-foobar'); // qunit-dom\n});\n```\n\n```js\nimport { test } from 'qunit';\n\ntest('foo', function () {\n  document.querySelector('#1234');\n});\n```\n\n```js\nimport { test } from 'qunit';\n\ntest('foo', function () {\n  this.element.querySelectorAll('..foobar');\n});\n```\n\n```js\nimport { find, click, fillIn, findAll, focus } from '@ember/test-helpers';\nimport { test } from 'qunit';\n\ntest('foo', function () {\n  find('[data-test-foobar');\n  findAll('[data-test-foobar');\n  fillIn('[data-test-foobar');\n  click('[data-test-foobar');\n  focus('[data-test-foobar');\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport { test } from 'qunit';\n\ntest('foo', function (assert) {\n  assert.dom('[data-test-foobar]'); // qunit-dom\n});\n```\n\n```js\nimport { test } from 'qunit';\n\ntest('foo', function () {\n  document.querySelector('#abcd');\n});\n```\n\n```js\nimport { test } from 'qunit';\n\ntest('foo', function () {\n  this.element.querySelectorAll('.foobar');\n});\n```\n\n```js\nimport { find, click, fillIn, findAll, focus } from '@ember/test-helpers';\nimport { test } from 'qunit';\n\ntest('foo', function () {\n  find('[data-test-foobar]');\n  findAll('[data-test-foobar]');\n  fillIn('[data-test-foobar]');\n  click('[data-test-foobar]');\n  focus('[data-test-foobar]');\n});\n```\n"
  },
  {
    "path": "docs/rules/route-path-style.md",
    "content": "# ember/route-path-style\n\n💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).\n\n<!-- end auto-generated rule header -->\n\nEnforces usage of kebab-case (instead of snake_case or camelCase) in route paths.\n\nA best practice on the web is to use kebab-case (hyphens) for separating words in URLs. This style is good for readability, clarity, SEO, etc.\n\nExample kebab-case URL: `https://guides.emberjs.com/release/getting-started/core-concepts/`\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nthis.route('blog_posts');\n```\n\n```js\nthis.route('blogPosts');\n```\n\n```js\nthis.route('blog-posts', { path: '/blog_posts' });\n```\n\n```js\nthis.route('blog-posts', { path: '/blogPosts' });\n```\n\nExamples of **correct** code for this rule:\n\n```js\nthis.route('blog-posts');\n```\n\n```js\nthis.route('blog_posts', { path: '/blog-posts' });\n```\n\n## References\n\n- [Ember Routing Guide](https://guides.emberjs.com/release/routing/)\n- [Keep a simple URL structure](https://support.google.com/webmasters/answer/76329) article by Google\n\n## Related Rules\n\n- [no-capital-letters-in-routes](no-capital-letters-in-routes.md)\n- [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md)\n- [routes-segments-snake-case](routes-segments-snake-case.md)\n"
  },
  {
    "path": "docs/rules/routes-segments-snake-case.md",
    "content": "# ember/routes-segments-snake-case\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDynamic segments in routes should use _snake case_, so Ember doesn't have to do extra serialization in order to resolve promises.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nthis.route('tree', { path: ':treeId' });\n```\n\nExamples of **correct** code for this rule:\n\n```js\nthis.route('tree', { path: ':tree_id' });\n```\n\n## References\n\n- [Ember Routing Guide](https://guides.emberjs.com/release/routing/)\n\n## Related Rules\n\n- [no-capital-letters-in-routes](no-capital-letters-in-routes.md)\n- [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md)\n- [route-path-style](route-path-style.md)\n"
  },
  {
    "path": "docs/rules/template-attribute-indentation.md",
    "content": "# ember/template-attribute-indentation\n\n<!-- end auto-generated rule header -->\n\nMigrated from [ember-template-lint/attribute-indentation](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/attribute-indentation.md).\n\n## Rule Details\n\nThis rule requires the positional params, attributes, and block params of helpers/components to be indented by moving them to multiple lines when the open invocation has more than 80 characters (configurable).\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                      | Type    | Choices                      |\n| :------------------------ | :------ | :--------------------------- |\n| `as-indentation`          |         | `attribute`, `closing-brace` |\n| `element-open-end`        |         | `new-line`, `last-attribute` |\n| `indentation`             | Integer |                              |\n| `mustache-open-end`       |         | `new-line`, `last-attribute` |\n| `open-invocation-max-len` | Integer |                              |\n| `process-elements`        | Boolean |                              |\n\n<!-- end auto-generated rule options list -->\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\nNon-block form (> 80 characters):\n\n```hbs\n{{employee-details firstName=firstName lastName=lastName age=age avatarUrl=avatarUrl}}\n```\n\nBlock form (> 80 characters):\n\n```hbs\n{{#employee-details\n  firstName=firstName lastName=lastName age=age avatarUrl=avatarUrl\n  as |employee|\n}}\n  {{employee.fullName}}\n{{/employee-details}}\n```\n\nHTML element (> 80 characters):\n\n```hbs\n<input disabled id='firstName' value={{firstName}} class='input-field first-name' type='text' />\n```\n\nExamples of **correct** code for this rule:\n\nNon-block form (attributes on separate lines):\n\n```hbs\n{{employee-details firstName=firstName lastName=lastName age=age avatarUrl=avatarUrl}}\n```\n\nBlock form (attributes on separate lines):\n\n```hbs\n{{#employee-details\n  firstName=firstName lastName=lastName age=age avatarUrl=avatarUrl\n  as |employee|\n}}\n  {{employee.fullName}}\n{{/employee-details}}\n```\n\nHTML element (attributes on separate lines):\n\n```hbs\n<input disabled id='firstName' value={{firstName}} class='input-field first-name' type='text' />\n```\n\nShort invocations (< 80 characters) are allowed on a single line:\n\n```hbs\n{{employee-details firstName=firstName lastName=lastName}}\n```\n\n## Options\n\n- `open-invocation-max-len` (integer, default `80`): Maximum length of the opening invocation before attributes must be on separate lines.\n- `indentation` (integer, default `2`): Number of spaces for attribute indentation.\n- `process-elements` (boolean, default `true`): Also validate indentation of HTML/SVG element attributes.\n- `element-open-end` (`\"new-line\"` | `\"last-attribute\"`, default `\"new-line\"`): Position of the closing `>` bracket.\n- `mustache-open-end` (`\"new-line\"` | `\"last-attribute\"`, default `\"new-line\"`): Position of the closing `}}` braces.\n- `as-indentation` (`\"attribute\"` | `\"closing-brace\"`, default `\"closing-brace\"`): Position of `as |param|` block params relative to attributes or closing brace.\n"
  },
  {
    "path": "docs/rules/template-attribute-order.md",
    "content": "# ember/template-attribute-order\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEnforces a consistent ordering of attributes in template elements. This helps improve readability and maintainability of templates.\n\n## Rule Details\n\nThis rule enforces a consistent order for attributes on template elements. By default, it follows this order:\n\n1. `class`\n2. `id`\n3. `role`\n4. `aria-*` attributes\n5. `data-test-*` attributes\n6. `type`\n7. `name`\n8. `value`\n9. `placeholder`\n10. `disabled`\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div id=\"main\" class=\"container\"></div>\n</template>\n```\n\n```gjs\n<template>\n  <button aria-label=\"Submit\" role=\"button\">Send</button>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div class=\"container\" id=\"main\"></div>\n</template>\n```\n\n```gjs\n<template>\n  <button class=\"btn\" role=\"button\" aria-label=\"Submit\">Send</button>\n</template>\n```\n\n## Configuration\n\nYou can customize the order by providing an `order` array:\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/template-attribute-order': [\n      'error',\n      {\n        order: ['class', 'id', 'role', 'aria-', 'type'],\n      },\n    ],\n  },\n};\n```\n\n## References\n\n- [ember-template-lint attribute-order](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/attribute-order.md)\n"
  },
  {
    "path": "docs/rules/template-block-indentation.md",
    "content": "# ember/template-block-indentation\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nMigrated from [ember-template-lint/block-indentation](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/block-indentation.md).\n\n## Rule Details\n\nForces valid indentation for blocks and their children.\n\n1. Forces block begin and block end statements to be at the same indentation level, when not on one line.\n2. Forces children of all blocks to start at a single indentation level deeper.\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name             | Type    |\n| :--------------- | :------ |\n| `ignoreComments` | Boolean |\n| `indentation`    | Integer |\n\n<!-- end auto-generated rule options list -->\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```hbs\n{{#each foo as |bar|}}{{/each}}\n```\n\n```hbs\n<div>\n  <p>{{t 'greeting'}}</p>\n</div>\n```\n\n```hbs\n<div>\n  <p>{{t 'Stuff here!'}}</p></div>\n```\n\nExamples of **correct** code for this rule:\n\n```hbs\n{{#each foo as |bar|}}\n  {{bar.name}}\n{{/each}}\n```\n\n```hbs\n<div>\n  <p>{{t 'greeting'}}</p>\n</div>\n```\n\n## Options\n\n- Integer (e.g., `2`, `4`): Number of spaces for indentation.\n- `\"tab\"`: Use tab-style indentation (1 character).\n- Object:\n  - `indentation` (integer, default `2`): Number of spaces to indent.\n  - `ignoreComments` (boolean, default `false`): Skip indentation checking for comments.\n\nWhen no option is specified, the rule reads `indent_size` from .editorconfig (if present).\nIf no .editorconfig is found, the default is `2` spaces.\n"
  },
  {
    "path": "docs/rules/template-builtin-component-arguments.md",
    "content": "# ember/template-builtin-component-arguments\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n✅ The `extends: 'recommended'` property in a configuration file enables this rule.\n\nThe builtin `Input` component has several arguments that match attributes\nof the lower-case `input` HTML element. These arguments should be set via e.g.\n`@type`, instead of `type`, but it is easy to forget and can cause subtle\nissues.\n\nThis rule warns about `Input` component invocations that use the following attributes instead of arguments:\n\n- `checked`\n- `type`\n- `value`\n\nThe builtin `Textarea` component has several arguments that match properties\nof the lower-case `textarea` HTML element. These arguments should be set via e.g.\n`@value`, instead of `value`, but it is easy to forget and can cause subtle\nissues.\n\nThis rule warns about `Textarea` component invocations that use the following attributes instead of arguments:\n\n- `value`\n\nPlease note that this rule currently only warns about these three attributes on\nthe `Input`, and one property on the `Textarea` components, but might be extended in the future to also warn about\nother attributes or builtin components.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<Input type='text' size='10' />\n```\n\n```hbs\n<Input @type='checkbox' checked />\n```\n\n```hbs\n<Textarea value=\"Hello, Tom!\" /></Textarea>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<input type='text' size='10' />\n```\n\n```hbs\n<Input @type='text' size='10' />\n```\n\n```hbs\n<Input @type='checkbox' @checked={{true}} />\n```\n\n```hbs\n<Textarea @value=\"Hello, Tom!\" /></Textarea>\n```\n\n## Migration\n\n- Add the `@` character in front of the relevant attributes to convert them\n  into component argument\n\n## Related Rules\n\n- [template-no-unknown-arguments-for-builtin-components](template-no-unknown-arguments-for-builtin-components.md)\n\n## References\n\n- [`Input` component API documentation](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input)\n- [`Textarea` component API documentation](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Textarea?anchor=Textarea)\n"
  },
  {
    "path": "docs/rules/template-deprecated-inline-view-helper.md",
    "content": "# ember/template-deprecated-inline-view-helper\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nIn Ember 1.12, support for invoking the inline View helper was deprecated.\n\n## Rule Details\n\nThis rule flags:\n\n- `{{view}}` mustache or block statements that have params or hash pair arguments (e.g., `{{view 'foo'}}`, `{{view 'foo' key=value}}`).\n- `{{view.property}}` path expressions (e.g., `{{view.also-bad}}`).\n- Hash values referencing `view.*` paths (e.g., `please=view.stop`).\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n{{view 'this-is-bad'}}\n\n{{view.also-bad}}\n\n{{qux-qaz please=view.stop}}\n\n{{#not-this please=view.stop}}{{/not-this}}\n\n<div foo={{view.bar}}></div>\n```\n\nThis rule **allows** the following:\n\n```hbs\n{{this-is-better}}\n\n{{qux-qaz this=good}}\n\n{{#ok-this yay=nice}}{{/ok-this}}\n\n<div foo={{bar}}></div>\n```\n\n## References\n\n- More information is available at the [Deprecation Guide](http://emberjs.com/deprecations/v1.x/#toc_ember-view).\n"
  },
  {
    "path": "docs/rules/template-deprecated-render-helper.md",
    "content": "# ember/template-deprecated-render-helper\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nDisallows the {{render}} helper which is deprecated.\n\n## Examples\n\nIncorrect:\n\n```hbs\n{{render 'user'}}\n```\n\nCorrect:\n\n```hbs\n<User />\n```\n\n## References\n\n- [eslint-plugin-ember template-deprecated-render-helper](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-deprecated-render-helper.md)\n"
  },
  {
    "path": "docs/rules/template-eol-last.md",
    "content": "# ember/template-eol-last\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nRequire or disallow newline at the end of template files.\n\n## Rule Details\n\nThis rule enforces at least one newline (or no newline) at the end of template files.\n\n## Config\n\nThis rule accepts a single string option:\n\n- `\"always\"` (default) — enforces that template files end with a newline\n- `\"editorconfig\"` — requires or disallows a final newline based on the project's `.editorconfig` settings (via `insert_final_newline`); throws if `insert_final_newline` is not set\n- `\"never\"` — enforces that template files do not end with a newline\n\n## Examples\n\nExamples of **incorrect** code with the default `\"always\"` config:\n\n```hbs\n<div>test</div>\n```\n\nExamples of **correct** code with the default `\"always\"` config:\n\n```hbs\n<div>test</div>\n{{! newline at end of file }}\n```\n\nExamples of **incorrect** code with the `\"never\"` config:\n\n```hbs\n<div>test</div>\n{{! trailing newline not allowed }}\n```\n\nExamples of **correct** code with the `\"never\"` config:\n\n```hbs\n<div>test</div>\n```\n\n## Related Rules\n\n- [eol-last](https://eslint.org/docs/rules/eol-last) from eslint\n\n## References\n\n- [ember-template-lint eol-last](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/eol-last.md)\n- [POSIX standard/line](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206)\n- [Wikipedia/newline](https://en.wikipedia.org/wiki/Newline#Interpretation)\n"
  },
  {
    "path": "docs/rules/template-linebreak-style.md",
    "content": "# ember/template-linebreak-style\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEnforce consistent linebreaks in templates.\n\nHaving consistent linebreaks is important to make sure that the source code is rendered correctly in editors.\n\n## Rule Details\n\nThis rule enforces consistent line endings in templates, independent of the operating system.\n\n## Config\n\nThis rule accepts a single string option:\n\n- `\"unix\"` (default) — enforces the usage of Unix line endings: `\\n` for LF\n- `\"windows\"` — enforces the usage of Windows line endings: `\\r\\n` for CRLF\n- `\"system\"` — enforces the usage of the current platform's line ending\n\n## Examples\n\nExamples of **incorrect** code with the default `\"unix\"` config:\n\n```hbs\n<div>test</div>\\r\\n\n```\n\nExamples of **correct** code with the default `\"unix\"` config:\n\n```hbs\n<div>test</div>\\n\n```\n\nExamples of **incorrect** code with the `\"windows\"` config:\n\n```hbs\n<div>test</div>\\n\n```\n\nExamples of **correct** code with the `\"windows\"` config:\n\n```hbs\n<div>test</div>\\r\\n\n```\n\n## Related Rules\n\n- [linebreak-style](https://eslint.org/docs/rules/linebreak-style) from eslint\n\n## References\n\n- [ember-template-lint linebreak-style](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/linebreak-style.md)\n- [Git/line endings](https://docs.github.com/en/github/using-git/configuring-git-to-handle-line-endings)\n"
  },
  {
    "path": "docs/rules/template-link-href-attributes.md",
    "content": "# ember/template-link-href-attributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nRequires `href` attribute on `<a>` elements.\n\nAnchor elements should have an `href` attribute to be properly recognized as links by browsers and assistive technologies. If an element is meant to be interactive but not navigate, use a `<button>` instead.\n\n## Rule Details\n\nThis rule ensures that all `<a>` elements have an `href` attribute.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <a>Link</a>\n</template>\n```\n\n```gjs\n<template>\n  <a onclick={{this.handleClick}}>Click me</a>\n</template>\n```\n\n```gjs\n<template>\n  <a role=\"button\">Action</a>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <a href=\"/about\">About Us</a>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"https://example.com\">External Link</a>\n</template>\n```\n\n```gjs\n<template>\n  <button {{on \"click\" this.handleClick}}>Click me</button>\n</template>\n```\n\n## References\n\n- [MDN: The Anchor element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)\n- [WebAIM: Links and Hypertext](https://webaim.org/techniques/hypertext/)\n- [ember-template-lint link-href-attributes](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/link-href-attributes.md)\n"
  },
  {
    "path": "docs/rules/template-link-rel-noopener.md",
    "content": "# ember/template-link-rel-noopener\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nWhen you want to link to an external page from your app, it is very common to use `<a href=\"url\" target=\"_blank\"></a>`\nto make the browser open this link in a new tab.\n\nHowever, this practice has [performance problems](https://jakearchibald.com/2016/performance-benefits-of-rel-noopener/)\nand also opens a door to some security attacks because the opened page can redirect the opener app\nto a malicious clone to perform phishing on your users.\n\nAdding `rel=\"noopener noreferrer\"` closes that door and avoids javascript in the opened tab to block the main\nthread in the opener. Also note that Firefox versions prior 52 do not implement `noopener`, so `rel=\"noreferrer\"` should be used instead ([see Firefox issue](https://bugzilla.mozilla.org/show_bug.cgi?id=1222516)).\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<a href='https://i.seem.secure.com' target='_blank'>I'm a bait</a>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<a href='https://i.seem.secure.com' target='_blank' rel='noopener noreferrer'>I'm a bait</a>\n```\n\n## References\n\n- [Link type \"noreferrer\"](https://html.spec.whatwg.org/multipage/semantics.html#link-type-noreferrer) spec\n"
  },
  {
    "path": "docs/rules/template-modifier-name-case.md",
    "content": "# ember/template-modifier-name-case\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nRequires dasherized names for modifiers.\n\nModifiers should use dasherized names when being invoked, not camelCase. This is a stylistic rule that will prevent you from using camelCase modifiers, requiring you to use dasherized modifier names instead.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><div {{didInsert}}></div></template>\n```\n\n```gjs\n<template><div {{onFocus}}></div></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><div {{did-insert}}></div></template>\n```\n\n```gjs\n<template><div {{on-focus}}></div></template>\n```\n\n## See Also\n\n- [named-functions-in-promises](named-functions-in-promises.md)\n\n## References\n\n- [Template syntax guide - Modifiers](https://guides.emberjs.com/release/components/template-syntax/#toc_modifiers)\n"
  },
  {
    "path": "docs/rules/template-no-abstract-roles.md",
    "content": "# ember/template-no-abstract-roles\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThe HTML attribute `role` must never have the following values:\n\n- `command`\n- `composite`\n- `input`\n- `landmark`\n- `range`\n- `roletype`\n- `section`\n- `sectionhead`\n- `select`\n- `structure`\n- `widget`\n- `window`\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<div role='window'> Hello, world! </div>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<div role='button'> Push it </div>\n```\n\n## References\n\n- See [https://www.w3.org/TR/wai-aria-1.0/roles#abstract_roles](https://www.w3.org/TR/wai-aria-1.0/roles#abstract_roles)\n"
  },
  {
    "path": "docs/rules/template-no-accesskey-attribute.md",
    "content": "# ember/template-no-accesskey-attribute\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows the use of `accesskey` attribute on elements.\n\nThe `accesskey` attribute creates inconsistencies between keyboard shortcuts and keyboard commands used by screen readers and keyboard-only users, causing accessibility issues.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <button accesskey=\"s\">Save</button>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"#\" accesskey=\"h\">Home</a>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <button>Save</button>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"#\">Home</a>\n</template>\n```\n\n## References\n\n- [eslint-plugin-ember no-accesskey-attribute](https://github.com/eslint-plugin-ember/eslint-plugin-ember/blob/master/docs/rule/no-accesskey-attribute.md)\n- [WebAIM: Keyboard Accessibility - Accesskey](https://webaim.org/techniques/keyboard/accesskey)\n"
  },
  {
    "path": "docs/rules/template-no-action-modifiers.md",
    "content": "# ember/template-no-action-modifiers\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.\n\nDisallow usage of `{{action}}` modifiers in templates.\n\nThe `{{action}}` modifier has been deprecated in favor of the `{{on}}` modifier. The `{{on}}` modifier provides a more explicit and flexible way to handle events.\n\n## Rule Details\n\nThis rule disallows using `{{action}}` as an element modifier.\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  <button {{action \"save\"}}>Save</button>\n</template>\n```\n\n```gjs\n<template>\n  <div {{action \"onClick\"}}>Click me</div>\n</template>\n```\n\n```gjs\n<template>\n  <form {{action \"submit\" on=\"submit\"}}>Submit</form>\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  <button {{on \"click\" this.handleClick}}>Save</button>\n</template>\n```\n\n```gjs\n<template>\n  <div {{on \"click\" this.onClick}}>Click me</div>\n</template>\n```\n\n```gjs\n<template>\n  <form {{on \"submit\" this.handleSubmit}}>Submit</form>\n</template>\n```\n\n## Options\n\n| Name        | Type       | Default | Description                                                              |\n| ----------- | ---------- | ------- | ------------------------------------------------------------------------ |\n| `allowlist` | `string[]` | `[]`    | List of element tag names where `{{action}}` modifiers should be allowed |\n\nThe option can be passed as an array (shorthand) or an object:\n\nShorthand:\n\n```json\n{\n  \"ember/template-no-action-modifiers\": [\"error\", [\"button\"]]\n}\n```\n\nObject form:\n\n```json\n{\n  \"ember/template-no-action-modifiers\": [\"error\", { \"allowlist\": [\"button\"] }]\n}\n```\n\n## Related Rules\n\n- [template-no-action](./template-no-action.md)\n\n## References\n\n- [Ember Octane Guide - Element Modifiers](https://guides.emberjs.com/release/components/template-lifecycle-dom-and-modifiers/)\n- [eslint-plugin-ember template-no-action](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-action.md)\n"
  },
  {
    "path": "docs/rules/template-no-action-on-submit-button.md",
    "content": "# ember/template-no-action-on-submit-button\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow click action on submit buttons within a form.\n\nIn a `<form>`, all `<button>` elements with a `type=\"submit\"` attribute (or no `type`, since buttons default to `type=\"submit\"`) should not have any click action. The action should be on the `<form>` element instead of directly on the button.\n\n## Rule Details\n\nThis rule disallows:\n\n- Using `{{action}}` or `{{on \"click\"}}` modifiers on submit buttons inside a `<form>`.\n- Using the HTML `action` attribute on submit buttons or `<input type=\"submit\">` elements.\n\n## Examples\n\n### Incorrect\n\n```hbs\n<form>\n  <button type='submit' {{on 'click' this.handleClick}} />\n  <button type='submit' {{action 'handleClick'}} />\n  <button {{on 'click' this.handleClick}} />\n  <button {{action 'handleClick'}} />\n</form>\n```\n\n### Correct\n\n```hbs\n<form>\n  <button type='button' {{on 'click' this.handleClick}} />\n  <button type='button' {{action 'handleClick'}} />\n  <button type='submit' />\n  <button />\n</form>\n```\n\nButtons outside a `<form>` are allowed to have click actions:\n\n```hbs\n<button type='submit' {{on 'click' this.handleClick}} />\n<button type='submit' {{action 'handleClick'}} />\n<button {{on 'click' this.handleClick}} />\n<button {{action 'handleClick'}} />\n```\n\n## Related Rules\n\n- [template-no-action-modifiers](./template-no-action-modifiers.md)\n\n## References\n\n- [eslint-plugin-ember template-no-invalid-interactive](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-invalid-interactive.md)\n"
  },
  {
    "path": "docs/rules/template-no-action.md",
    "content": "# ember/template-no-action\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows the use of `{{action}}` helper.\n\nThe `{{action}}` helper is deprecated in favor of the `{{on}}` modifier and `{{fn}}` helper, which provide better performance and clearer intent.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <button {{on \"click\" (action \"save\")}}>Save</button>\n</template>\n```\n\n```gjs\n<template>\n  {{action \"doSomething\"}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <button {{on \"click\" this.save}}>Save</button>\n</template>\n```\n\n```gjs\n<template>\n  <button {{on \"click\" (fn this.save \"arg\")}}>Save with arg</button>\n</template>\n```\n\n```gjs\n<template>\n  {{this.action}}\n</template>\n```\n\n```gjs\nimport action from './my-action-helper';\n<template>\n  {{action this.handleClick}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each items as |action|}}\n    <button {{action this.handleClick}}>x</button>\n  {{/each}}\n</template>\n```\n\n## Strict-mode behavior\n\n`action` is an ambient strict-mode keyword in Ember (registered in `STRICT_MODE_KEYWORDS`), so `{{action this.x}}` works in `.gjs`/`.gts` templates without an explicit import. The rule still flags those uses to discourage the deprecated keyword — but skips reports when `action` resolves to a JS-scope binding (an import or local declaration) or a template block param.\n\n## Migration\n\n- Replace `(action \"methodName\")` with method references or `(fn this.methodName)`\n- Replace `<button onclick={{action ...}}>` with `<button {{on \"click\" ...}}>`\n\n## References\n\n- [eslint-plugin-ember template-no-action](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-action.md)\n- [Ember.js Deprecations - action helper](https://deprecations.emberjs.com/v3.x/#toc_action-helper)\n- [Ember Modifier Documentation](https://guides.emberjs.com/release/components/template-lifecycle-dom-and-modifiers/)\n"
  },
  {
    "path": "docs/rules/template-no-args-paths.md",
    "content": "# ember/template-no-args-paths\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nArguments that are passed to components are prefixed with the `@` symbol in Angle bracket syntax.\nEmber Octane leverages this in the component's templates by allowing users to directly refer to an argument using the same prefix:\n\n```gjs\n<template>\n  <!-- todo-list.hbs -->\n  <ul>\n    {{#each @todos as |todo index|}}\n      <li>\n        {{yield (todo-item-component todo=todo) index}}\n      </li>\n    {{/each}}\n  </ul>\n</template>\n```\n\nWe can immediately tell now by looking at this template that `@todos` is an argument that was passed to the component externally. This is in fact _always true_ - there is no way to modify the value referenced by `@todos` from the component class, it is the original, unmodified value.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{this.args.foo}}\n  {{args.foo}}\n</template>\n```\n\n```gjs\n<template>\n  {{my-helper this.args.foo}}\n  {{my-helper (hash value=this.args.foo)}}\n</template>\n```\n\n```gjs\n<template>\n  <MyComponent @value={{this.args.foo}} />\n  <div {{my-modifier this.args.foo}}></div>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{my-helper this.args}}\n  {{my-helper (hash value=this.args)}}\n</template>\n```\n\n```gjs\n<template>\n  {{@foo}}\n  <MyComponent @value={{@foo}} />\n  <div {{my-modifier @foo}}></div>\n</template>\n```\n\n## Migration\n\n- find in templates `this.args.` replace to `@`\n\n## Related Rules\n\n- [no-curly-component-invocation](no-curly-component-invocation.md)\n\n## References\n\n- [RFC #276](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md)\n- [Coming Soon in Ember Octane - Part 2: Named Argument Syntax](https://www.pzuraq.com/blog/coming-soon-in-ember-octane-part-2-angle-brackets-and-named-arguments/#namedargumentsyntax)\n- [Named arguments in Ember.js](https://www.balinterdi.com/blog/named-arguments-in-ember-js/)\n- [ember-named-arguments-polyfill](https://github.com/rwjblue/ember-named-arguments-polyfill)\n"
  },
  {
    "path": "docs/rules/template-no-arguments-for-html-elements.md",
    "content": "# ember/template-no-arguments-for-html-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.\n\nDisallow `@arguments` on HTML elements.\n\nArguments (using the `@` prefix) are a feature specific to Ember components. They should not be used on regular HTML elements, which only support standard HTML attributes.\n\n## Rule Details\n\nThis rule disallows using `@arguments` on HTML elements. Use regular attributes instead.\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  <div @title=\"Hello\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <button @onClick={{this.handler}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <span @data={{this.info}}>Text</span>\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  <div title=\"Hello\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <button {{on \"click\" this.handler}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <MyComponent @title=\"Hello\" @onClick={{this.handler}} />\n</template>\n```\n\n## Related Rules\n\n- [template-no-block-params-for-html-elements](./template-no-block-params-for-html-elements.md)\n\n## References\n\n- [Ember Guides - Component Arguments](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/)\n- [eslint-plugin-ember template-no-args-paths](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-args-paths.md)\n"
  },
  {
    "path": "docs/rules/template-no-aria-hidden-body.md",
    "content": "# ember/template-no-aria-hidden-body\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThe aria-hidden attribute should never be present on the `<body>` element, as it hides the entire document from assistive technology.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<body aria-hidden>\n```\n\n```hbs\n<body aria-hidden=\"true\">\n```\n\nThis rule **allows** the following:\n\n```hbs\n<body>\n```\n\n## References\n\n- [Using the aria-hidden attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute)\n- [How Lighthouse identifies hidden body elements](https://web.dev/aria-hidden-body/)\n- [WCAG 4.1.2 - Name, Role, Value (Level A)](https://www.w3.org/TR/WCAG21/#name-role-value)\n"
  },
  {
    "path": "docs/rules/template-no-aria-unsupported-elements.md",
    "content": "# ember/template-no-aria-unsupported-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows using ARIA roles, states, and properties on elements that do not support them.\n\nCertain HTML elements do not support ARIA roles, states, and properties. This rule helps ensure that ARIA attributes are only used on elements that support them, improving accessibility.\n\n## Rule Details\n\nThis rule disallows ARIA attributes on elements that do not support them, including:\n\n- `meta`\n- `html`\n- `script`\n- `style`\n- `title`\n- `base`\n- `head`\n- `link`\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <meta role=\"button\" />\n</template>\n```\n\n```gjs\n<template>\n  <script aria-label=\"Analytics\"></script>\n</template>\n```\n\n```gjs\n<template>\n  <style role=\"presentation\"></style>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div role=\"button\" aria-label=\"Submit\"></div>\n</template>\n```\n\n```gjs\n<template>\n  <button aria-pressed=\"true\">Toggle</button>\n</template>\n```\n\n## References\n\n- [WAI-ARIA in HTML](https://www.w3.org/TR/html-aria/)\n- [ember-template-lint no-aria-unsupported-elements](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-aria-unsupported-elements.md)\n"
  },
  {
    "path": "docs/rules/template-no-array-prototype-extensions.md",
    "content": "# ember/template-no-array-prototype-extensions\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.\n\nDisallow usage of Ember Array prototype extensions.\n\nEmber historically provided Array prototype extensions like `firstObject` and `lastObject`. These extensions are deprecated and should be replaced with native JavaScript array methods or computed properties.\n\n## Rule Details\n\nThis rule disallows using Ember Array prototype extensions in templates:\n\n- `firstObject`\n- `lastObject`\n- `@each`\n- `[]`\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  {{this.items.firstObject}}\n</template>\n```\n\n```gjs\n<template>\n  {{this.users.lastObject}}\n</template>\n```\n\n```gjs\n<template>\n  {{this.data.@each}}\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  {{get this.items 0}}\n</template>\n```\n\n```gjs\n<template>\n  {{this.firstItem}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items as |item|}}\n    {{item}}\n  {{/each}}\n</template>\n```\n\n## Related Rules\n\n- [no-array-prototype-extensions](./no-array-prototype-extensions.md)\n\n## References\n\n- [Ember Deprecations - Array prototype extensions](https://deprecations.emberjs.com/v3.x/#toc_ember-array-prototype-extensions)\n- [eslint-plugin-ember template-no-array-prototype-extensions](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-array-prototype-extensions.md)\n"
  },
  {
    "path": "docs/rules/template-no-at-ember-render-modifiers.md",
    "content": "# ember/template-no-at-ember-render-modifiers\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows usage of modifiers from @ember/render-modifiers.\n\n## Rule Details\n\nThe modifiers from `@ember/render-modifiers` (`{{did-insert}}`, `{{did-update}}`, `{{will-destroy}}`) should be replaced with alternatives from `ember-render-helpers` or other modern approaches.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div {{did-insert this.setup}}></div>\n</template>\n```\n\n```gjs\n<template>\n  <div {{did-update this.update}}></div>\n</template>\n```\n\n```gjs\n<template>\n  <div {{will-destroy this.cleanup}}></div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div {{on \"click\" this.handleClick}}></div>\n</template>\n```\n\n## Migration\n\nThe migration path typically depends on what the render-modifier was used for, but if you need a custom modifier, the [`ember-modifier` README](https://github.com/ember-modifier/ember-modifier) covers everything you need to know for making custom modifiers.\n\nFor example, if render modifiers were used for setup/teardown, the migration to `ember-modifier` could be the following:\n\n```js\nimport Component from '@glimmer/component';\nimport { modifier } from 'ember-modifier';\n\nexport default class MyComponent extends Component {\n  myModifier = modifier((element) => {\n    const handleEvent = () => {};\n\n    element.addEventListener('eventName', handleEvent);\n\n    return () => element.removeEventListener('eventName', handelEvent);\n  });\n}\n```\n\n```hbs\n<div {{this.myModifier}}>\n```\n\n## References\n\n- [eslint-plugin-ember template-no-at-ember-render-modifiers](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-at-ember-render-modifiers.md)\n"
  },
  {
    "path": "docs/rules/template-no-attrs-in-components.md",
    "content": "# ember/template-no-attrs-in-components\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule prevents the usage of `this.attrs` property to access values passed to the component. Use `@arg` syntax instead.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n{{this.attrs.foo}}\n```\n\nThis rule **allows** the following:\n\n```hbs\n{{@foo}}\n```\n\n## References\n\n- [rfcs/named args](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md#motivation)\n"
  },
  {
    "path": "docs/rules/template-no-autofocus-attribute.md",
    "content": "# ember/template-no-autofocus-attribute\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows the use of `autofocus` attribute on elements.\n\nThe `autofocus` attribute can cause usability issues for both sighted and non-sighted users by disrupting expected behavior and screen reader announcements.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <input type=\"text\" autofocus />\n</template>\n```\n\n```gjs\n<template>\n  <textarea autofocus></textarea>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <input type=\"text\" />\n</template>\n```\n\n```gjs\n<template>\n  <textarea></textarea>\n</template>\n```\n\nExplicit opt-out via a mustache boolean `false` is allowed — this is the\nonly form that statically guarantees no rendered `autofocus` attribute\n(Glimmer VM normalizes `{{false}}` to attribute removal). The string\n`autofocus=\"false\"` is still flagged per HTML boolean-attribute semantics\n(any attribute presence, including the string `\"false\"`, enables autofocus).\n\n```gjs\n<template>\n  <input autofocus={{false}} />\n  {{!-- element syntax: the mustache-boolean form --}}\n\n  {{input autofocus=false}}\n  {{!-- mustache syntax: the hash-pair form --}}\n</template>\n```\n\n`<dialog>` and its descendants are exempt. A dialog is expected to focus its\ninitial element on open, per\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog):\n\n```gjs\n<template>\n  <dialog>\n    <button autofocus>Close</button>\n  </dialog>\n</template>\n```\n\n## When Not To Use It\n\nIf you need to autofocus for specific accessibility or UX requirements and have thoroughly tested with assistive technologies, you may disable this rule for those specific cases.\n\n## References\n\n- [eslint-plugin-ember template-no-autofocus-attribute](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-autofocus-attribute.md)\n- [MDN autofocus attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus)\n"
  },
  {
    "path": "docs/rules/template-no-bare-strings.md",
    "content": "# ember/template-no-bare-strings\n\n<!-- end auto-generated rule header -->\n\nDisallows bare strings in templates to encourage internationalization.\n\nBare strings in templates make internationalization (i18n) difficult. This rule encourages using translation helpers or properties to enable easy localization of your application.\n\n## Rule Details\n\nThis rule disallows text content in templates that isn't wrapped in a translation helper or passed as a property.\n\nThe following are allowed:\n\n- Whitespace-only strings\n- Strings in the default allowlist (punctuation characters like `(`, `)`, `.`, `&`, etc.)\n- Strings in a custom allowlist (configurable)\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div>Hello World</div>\n</template>\n```\n\n```gjs\n<template>\n  <button>Click me</button>\n</template>\n```\n\n```gjs\n<template>\n  <h1>Welcome to our app</h1>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>{{t \"hello.world\"}}</div>\n</template>\n```\n\n```gjs\n<template>\n  <button>{{@buttonText}}</button>\n</template>\n```\n\n```gjs\n<template>\n  <div>   </div>\n</template>\n```\n\n## Configuration\n\n### `allowlist`\n\nAn array of strings that are allowed to appear as bare strings:\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/template-no-bare-strings': [\n      'error',\n      {\n        allowlist: ['Welcome', 'Home', 'About'],\n      },\n    ],\n  },\n};\n```\n\n### `globalAttributes`\n\nAn array of attribute names where bare strings will be checked globally on all elements (defaults to `[\"title\", \"aria-label\", \"aria-placeholder\", \"aria-roledescription\", \"aria-valuetext\"]`):\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/template-no-bare-strings': [\n      'error',\n      {\n        globalAttributes: [\n          'title',\n          'aria-label',\n          'aria-placeholder',\n          'aria-roledescription',\n          'aria-valuetext',\n        ],\n      },\n    ],\n  },\n};\n```\n\n### `elementAttributes`\n\nAn object mapping element names to arrays of attribute names to check for bare strings (defaults to `{ input: [\"placeholder\"], img: [\"alt\"] }`). The built-in Ember components `Input` and `Textarea` also check `placeholder` and `@placeholder`:\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/template-no-bare-strings': [\n      'error',\n      {\n        elementAttributes: {\n          input: ['placeholder'],\n          img: ['alt'],\n        },\n      },\n    ],\n  },\n};\n```\n\n### `ignoredElements`\n\nAn array of element names whose text content is ignored (defaults to `[\"pre\", \"script\", \"style\", \"textarea\"]`):\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/template-no-bare-strings': [\n      'error',\n      {\n        ignoredElements: ['pre', 'script', 'style', 'textarea'],\n      },\n    ],\n  },\n};\n```\n\n## References\n\n- [eslint-plugin-ember template-no-bare-strings](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-bare-strings.md)\n- [Ember Intl](https://github.com/ember-intl/ember-intl)\n"
  },
  {
    "path": "docs/rules/template-no-bare-yield.md",
    "content": "# ember/template-no-bare-yield\n\n<!-- end auto-generated rule header -->\n\nDisallow `{{yield}}` without parameters outside of contextual components.\n\n## Rule Details\n\nThis rule enforces passing parameters to `{{yield}}` to make component APIs more explicit.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{yield}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  {{yield (Object greeting=\"hello there\")}}\n</template>\n\n<template>\n  {{yield @model}}\n</template>\n```\n"
  },
  {
    "path": "docs/rules/template-no-block-params-for-html-elements.md",
    "content": "# ember/template-no-block-params-for-html-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow block params on HTML elements.\n\nBlock params (using the `as |param|` syntax) are a feature specific to Ember components and block helpers. They should not be used on regular HTML elements.\n\n## Rule Details\n\nThis rule disallows using block params on HTML elements. Use components if you need to pass block params.\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  <div as |content|>\n    {{content}}\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <section as |data|>\n    <p>{{data}}</p>\n  </section>\n</template>\n```\n\n```gjs\n<template>\n  <ul as |items|>\n    <li>{{items}}</li>\n  </ul>\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  <div>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <MyComponent as |item|>\n    {{item.name}}\n  </MyComponent>\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items as |item|}}\n    <li>{{item}}</li>\n  {{/each}}\n</template>\n```\n\n## Related Rules\n\n- [template-no-arguments-for-html-elements](./template-no-arguments-for-html-elements.md)\n\n## References\n\n- [Ember Guides - Block Content](https://guides.emberjs.com/release/components/block-content/)\n- [eslint-plugin-ember template-no-yield-only](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-yield-only.md)\n"
  },
  {
    "path": "docs/rules/template-no-builtin-form-components.md",
    "content": "# ember/template-no-builtin-form-components\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow usage of Ember's built-in `<Input>` and `<Textarea>` components. These components use two-way binding to mutate values, which is considered an anti-pattern. Use native HTML `<input>` and `<textarea>` elements instead.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><Input @type=\"text\" @value={{this.name}} /></template>\n```\n\n```gjs\n<template><Textarea @value={{this.body}}></Textarea></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><input type=\"text\" value={{this.name}} {{on \"input\" this.handleInput}} /></template>\n```\n\n```gjs\n<template><textarea {{on \"input\" this.handleInput}}>{{this.body}}</textarea></template>\n```\n\n## Migration\n\nMany forms may be simplified by switching to a light one-way data approach.\n\nFor example – vanilla JavaScript has everything we need to handle form data, de-sync it from our source data and collect all user input in a single object.\n\n```js\nimport Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\n\nexport default class MyComponent extends Component {\n  @tracked userInput = {};\n\n  @action\n  handleInput(event) {\n    const formData = new FormData(event.currentTarget);\n    this.userInput = Object.fromEntries(formData.entries());\n  }\n}\n```\n\n```hbs\n<form {{on 'input' this.handleInput}}>\n  <label>\n    Name\n    <input name='name' />\n  </label>\n</form>\n```\n\nAnother option would is to \"control\" the field's value by replacing the built-in form component with a native HTML element and binding an event listener to handle user input.\n\nIn the following example the initial value of a field is controlled by a local tracked property, which is updated by an event listener.\n\n```js\nimport Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\n\nexport default class MyComponent extends Component {\n  @tracked name;\n\n  @action\n  updateName(event) {\n    this.name = event.target.value;\n  }\n}\n```\n\n```hbs\n<input type='text' value={{this.name}} {{on 'input' this.updateName}} />\n```\n\n## Related Rules\n\n- [no-mut-helper](template-no-mut-helper.md)\n\n## References\n\n- [Ember Built-in Components](https://guides.emberjs.com/release/components/built-in-components/)\n- [ember-template-lint no-builtin-form-components](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-builtin-form-components.md)\n"
  },
  {
    "path": "docs/rules/template-no-capital-arguments.md",
    "content": "# ember/template-no-capital-arguments\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow capital letters in argument names. Use lowercase argument names (e.g., `@arg` instead of `@Arg`).\n\n## Rule Details\n\nThis rule enforces the convention that argument names should start with lowercase letters.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div>{{@Arg}}</div>\n</template>\n```\n\n```gjs\n<template>\n  <div>{{@MyArgument}}</div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>{{@arg}}</div>\n</template>\n```\n\n```gjs\n<template>\n  <div>{{@myArgument}}</div>\n</template>\n```\n\n## References\n\n- [Ember Style Guide](https://github.com/ember-cli/ember-styleguide)\n"
  },
  {
    "path": "docs/rules/template-no-chained-this.md",
    "content": "# ember/template-no-chained-this\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow redundant `this.this` in templates.\n\nUsing `this.this.*` in templates is almost always a typo or copy/paste mistake. These patterns are misleading and result in unnecessary ambiguity about scope and component context.\n\n## Rule Details\n\nThis rule disallows `this.this.*` patterns in templates (e.g., `{{this.this.foo}}` or `<this.this.Bar />`).\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  {{this.this.value}}\n</template>\n```\n\n```gjs\n<template>\n  {{#this.this.foo}}\n    some text\n  {{/this.this.foo}}\n</template>\n```\n\n```gjs\n<template>\n  {{helper value=this.this.foo}}\n</template>\n```\n\n```gjs\n<template>\n  <this.this.Component />\n</template>\n```\n\n```gjs\n<template>\n  {{component this.this.dynamicComponent}}\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  {{this.value}}\n</template>\n```\n\n```gjs\n<template>\n  <this.Component />\n</template>\n```\n\n```gjs\n<template>\n  {{component this.dynamicComponent}}\n</template>\n```\n\n```gjs\n<template>\n  {{@argName}}\n</template>\n```\n\n## Migration\n\nRemove the extra `this`:\n\nBefore:\n\n```gjs\n<template>\n  {{this.this.foo}}\n  <this.this.bar />\n</template>\n```\n\nAfter:\n\n```gjs\n<template>\n  {{this.foo}}\n  <this.bar />\n</template>\n```\n\n## References\n\n- [Ember Guides - Glimmer Component Templates](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/)\n- [Handlebars Strict Mode](https://github.com/emberjs/rfcs/blob/master/text/0496-handlebars-strict-mode.md)\n"
  },
  {
    "path": "docs/rules/template-no-class-bindings.md",
    "content": "# ember/template-no-class-bindings\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nDisallow passing `classBinding` or `classNameBindings` as arguments within templates. These are legacy Ember Classic patterns that should be replaced with modern approaches.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<SomeThing @classBinding='isActive:active' />\n```\n\n```hbs\n{{some-thing classNameBindings='isActive:active:inactive'}}\n```\n\n```hbs\n<SomeThing @classNameBindings='isActive:active:inactive' />\n```\n\nThis rule **allows** the following:\n\n```hbs\n<SomeThing class={{if this.isActive 'active'}} />\n```\n\n```hbs\n<SomeThing />\n```\n\n## Migration\n\n- find in templates and remove `classBinding` and/or `classNameBindings`.\n\n## References\n\n- [ember-template-lint no-class-bindings](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-class-bindings.md)\n"
  },
  {
    "path": "docs/rules/template-no-curly-component-invocation.md",
    "content": "# ember/template-no-curly-component-invocation\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nDisallows curly component invocation syntax. Use angle bracket syntax instead.\n\nThere are two ways to invoke a component in a template: curly component syntax\n(`{{my-component}}`), and angle bracket syntax (`<MyComponent />`). The\ndifference between them is syntactical. You should favour angle bracket syntax\nas it improves readability of templates, i.e. disambiguates components from\nhelpers, and is also the future direction Ember is going with the Octane\nEdition.\n\nThis rule checks all the curly braces in your app and warns about those that\nlook like they could be component invocations.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n{{foo-bar}}\n```\n\n```hbs\n{{nested/component}}\n```\n\n```hbs\n{{#foo-bar}}content{{/foo-bar}}\n```\n\nThis rule **allows** the following:\n\n```hbs\n{{foo bar}}\n```\n\n```hbs\n<FooBar />\n```\n\n```hbs\n<Nested::Component />\n```\n\n## Migration\n\n- use <https://github.com/ember-codemods/ember-angle-brackets-codemod>\n\n## Configuration\n\nThis rule accepts an options object with the following properties:\n\n- `allow` (default: `[]`) - Array of component names to allow in curly syntax\n- `disallow` (default: `[]`) - Array of component names to disallow in curly syntax\n- `requireDash` (default: `false`) - Require dashes in component names\n- `noImplicitThis` (default: `true`) - Don't allow implicit `this` references\n\n```js\n// .eslintrc.js\nmodule.exports = {\n  rules: {\n    'ember/template-no-curly-component-invocation': [\n      'error',\n      {\n        allow: ['some-helper'],\n        disallow: [],\n      },\n    ],\n  },\n};\n```\n\n## References\n\n- [Ember Guides - Angle Bracket Syntax](https://guides.emberjs.com/release/components/template-syntax/#toc_angle-bracket-syntax)\n"
  },
  {
    "path": "docs/rules/template-no-debugger.md",
    "content": "# ember/template-no-debugger\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows usage of `{{debugger}}` in templates.\n\nThe `{{debugger}}` helper is useful for debugging but should not be present in production code.\n\n## Rule Details\n\nThis rule disallows the use of `{{debugger}}` statements in templates.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{debugger}}\n  <div>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  {{#if condition}}\n    {{debugger}}\n  {{/if}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  {{this.debug}}\n</template>\n```\n\n## Related Rules\n\n- [no-debugger](https://eslint.org/docs/rules/no-debugger) from ESLint\n\n## References\n\n- [ember-template-lint no-debugger](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-debugger.md)\n"
  },
  {
    "path": "docs/rules/template-no-deprecated.md",
    "content": "# ember/template-no-deprecated\n\n<!-- end auto-generated rule header -->\n\nDisallows using components, helpers, or modifiers that are marked `@deprecated` in their JSDoc.\n\nThis rule requires TypeScript (`parserServices.program` must be present). It is a no-op in plain `.gjs` files because cross-file import deprecations require type information.\n\n## Rule Details\n\nThis rule checks if imported Glimmer components, helpers, or modifiers are marked `@deprecated` in their JSDoc.\n\n**Covered syntax:**\n\n| Template syntax         | Example                                     |\n| ----------------------- | ------------------------------------------- |\n| Component element       | `<DeprecatedComponent />`                   |\n| Helper / value mustache | `{{deprecatedHelper}}`                      |\n| Block component         | `{{#DeprecatedBlock}}…{{/DeprecatedBlock}}` |\n| Modifier                | `<div {{deprecatedModifier}}>`              |\n| Component argument      | `<MyComp @deprecatedArg={{x}}>`             |\n\n## Examples\n\nGiven a module:\n\n```ts\n// deprecated-component.ts\n/** @deprecated use NewComponent instead */\nexport default class DeprecatedComponent {}\n```\n\nExamples of **incorrect** code for this rule:\n\n```gts\nimport DeprecatedComponent from './deprecated-component';\n\n<template>\n  <DeprecatedComponent />\n</template>\n```\n\n```gts\nimport { deprecatedHelper } from './deprecated-helper';\n\n<template>\n  {{deprecatedHelper}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gts\nimport NewComponent from './new-component';\n\n<template>\n  <NewComponent />\n</template>\n```\n"
  },
  {
    "path": "docs/rules/template-no-duplicate-attributes.md",
    "content": "# ember/template-no-duplicate-attributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows duplicate attribute names in templates.\n\nDuplicate attributes on the same element can lead to unexpected behavior and are often a mistake.\n\n## Rule Details\n\nThis rule disallows duplicate attributes on HTML elements, components, and helpers.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div class=\"foo\" class=\"bar\"></div>\n</template>\n```\n\n```gjs\n<template>\n  <input type=\"text\" disabled type=\"email\" />\n</template>\n```\n\n```gjs\n<template>\n  {{helper foo=\"bar\" foo=\"baz\"}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div class=\"foo bar\"></div>\n</template>\n```\n\n```gjs\n<template>\n  <input type=\"email\" disabled />\n</template>\n```\n\n```gjs\n<template>\n  {{helper foo=\"bar\" baz=\"qux\"}}\n</template>\n```\n\n## References\n\n- [eslint-plugin-ember template-no-duplicate-attributes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-duplicate-attributes.md)\n"
  },
  {
    "path": "docs/rules/template-no-duplicate-id.md",
    "content": "# ember/template-no-duplicate-id\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nValid HTML requires that `id` attribute values are unique.\n\nThis rule does a basic check to ensure that `id` attribute values are not the same.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><div id='id-00'></div><div id='id-00'></div></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><div id={{this.divId}}></div></template>\n```\n\n```gjs\n<template><div id='concat-{{this.divId}}'></div></template>\n```\n\n```gjs\n<template>\n  <MyComponent as |inputProperties|>\n    <Input id={{inputProperties.id}} />\n    <div id={{inputProperties.abc}} />\n  </MyComponent>\n\n  <MyComponent as |inputProperties|>\n    <Input id={{inputProperties.id}} />\n  </MyComponent>\n</template>\n```\n\n## Migration\n\nFor best results, it is recommended to generate `id` attribute values when they are needed, to ensure that they are not duplicates.\n\n## References\n\n- <https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#the-id-attribute>\n"
  },
  {
    "path": "docs/rules/template-no-duplicate-landmark-elements.md",
    "content": "# ember/template-no-duplicate-landmark-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nIf multiple landmark elements of the same type are found on a page, they must each have a unique label (provided by `aria-label` or `aria-labelledby`).\n\n## Rule Details\n\nList of elements & their corresponding roles:\n\n- `header` (banner)\n- `main` (main)\n- `aside` (complementary)\n- `form` (form, search)\n- `nav` (navigation)\n- `footer` (contentinfo)\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<nav></nav>\n<nav></nav>\n```\n\n```hbs\n<nav></nav>\n<div role='navigation'></div>\n```\n\n```hbs\n<nav aria-label='site navigation'></nav>\n<nav aria-label='site navigation'></nav>\n```\n\n```hbs\n<form aria-label='search-form'></form>\n<form aria-label='search-form'></form>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<nav aria-label='primary site navigation'></nav>\n<nav aria-label='secondary site navigation within home page'></nav>\n```\n\n```hbs\n<nav aria-label='primary site navigation'></nav>\n<div role='navigation' aria-label='secondary site navigation within home page'></div>\n```\n\n```hbs\n<form aria-label='shipping address'></form>\n<form aria-label='billing address'></form>\n```\n\n```hbs\n<form role='search' aria-label='search'></form>\n<form aria-labelledby='form-title'><div id='form-title'>Meaningful Form Title</div></form>\n```\n\n## References\n\n- [WAI-ARIA specification: Landmark Roles](https://www.w3.org/WAI/PF/aria/roles#landmark_roles)\n- [Understanding Success Criterion 1.3.1: Info and Relationships](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html)\n- [Using aria-labelledby to name regions and landmarks](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA13.html)\n- [Using aria-label to provide labels for objects](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA6)\n"
  },
  {
    "path": "docs/rules/template-no-dynamic-subexpression-invocations.md",
    "content": "# ember/template-no-dynamic-subexpression-invocations\n\n<!-- end auto-generated rule header -->\n\nDisallow dynamic helper invocations.\n\nDynamic helper invocations (where the helper name comes from a property or argument) make code harder to understand and can have performance implications. Use explicit helper names instead.\n\n## Rule Details\n\nThis rule disallows invoking helpers dynamically using `this` or `@` properties.\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  {{(this.helper \"arg\")}}\n</template>\n```\n\n```gjs\n<template>\n  {{(@helperName \"value\")}}\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  {{format-date this.date}}\n</template>\n```\n\n```gjs\n<template>\n  {{(upper-case this.name)}}\n</template>\n```\n\n```gjs\n<template>\n  {{this.formattedData}}\n</template>\n```\n\n```gjs\n{{! Body-position dynamic helpers are allowed }}\n<template>\n  {{this.formatter this.data}}\n</template>\n```\n\n## Related Rules\n\n- [template-no-implicit-this](./template-no-implicit-this.md)\n\n## References\n\n- [Ember Guides - Template Helpers](https://guides.emberjs.com/release/components/helper-functions/)\n- [eslint-plugin-ember template-no-dynamic-subexpression-invocations](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-dynamic-subexpression-invocations.md)\n"
  },
  {
    "path": "docs/rules/template-no-element-event-actions.md",
    "content": "# ember/template-no-element-event-actions\n\n<!-- end auto-generated rule header -->\n\nDisallow using element event actions (e.g., `onclick={{action}}`) in templates. Use the `{{on}}` modifier instead.\n\n## Rule Details\n\nThis rule disallows the use of element event actions in templates.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <button onclick={{this.handleClick}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <div onmouseenter={{this.handleHover}}>Hover</div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <button {{on \"click\" this.handleClick}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <div {{on \"mouseenter\" this.handleHover}}>Hover</div>\n</template>\n```\n\n## Options\n\n| Name                  | Type      | Default | Description                                                                                                       |\n| --------------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------- |\n| `requireActionHelper` | `boolean` | `false` | When `true`, only flags events using `{{action ...}}`; when `false`, flags any dynamic value on event attributes. |\n\n## References\n\n- [Ember Octane migration guide](https://guides.emberjs.com/release/upgrading/current-edition/action-on-and-fn/)\n"
  },
  {
    "path": "docs/rules/template-no-empty-headings.md",
    "content": "# ember/template-no-empty-headings\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nHeadings relay the structure of a webpage and provide a meaningful, hierarchical order of its content. If headings are empty or its text contents are inaccessible, this could confuse users or prevent them accessing sections of interest.\n\nDisallow headings (h1, h2, etc.) with no accessible text content.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><h*></h*></template>\n```\n\n```gjs\n<template><div role='heading' aria-level='1'></div></template>\n```\n\n```gjs\n<template><h*><span aria-hidden='true'>Inaccessible text</span></h*></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><h*>Heading Content</h*></template>\n```\n\n```gjs\n<template><h*><span>Text</span><h*></template>\n```\n\n```gjs\n<template><div role='heading' aria-level='1'>Heading Content</div></template>\n```\n\n```gjs\n<template><h* aria-hidden='true'>Heading Content</h*></template>\n```\n\n```gjs\n<template><h* hidden>Heading Content</h*></template>\n```\n\n## Migration\n\nIf violations are found, remediation should be planned to ensure text content is present and visible and/or screen-reader accessible. Setting `aria-hidden=\"false\"` or removing `hidden` attributes from the element(s) containing heading text may serve as a quickfix.\n\n## Notes on `aria-hidden` semantics\n\nThis rule follows [WAI-ARIA 1.2 §`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden) verbatim: only an explicit truthy value hides the element. Ambiguous shapes — valueless `aria-hidden`, empty string, and mustache literals that resolve to an empty / whitespace-only string — all resolve to the default `undefined` and do NOT exempt the heading from the empty-content check.\n\n- `aria-hidden=\"true\"` / `aria-hidden={{true}}` / `aria-hidden={{\"true\"}}` (any case, whitespace-trimmed) → hidden, exempts the heading.\n- `aria-hidden=\"false\"` / `aria-hidden={{false}}` / `aria-hidden={{\"false\"}}` → not hidden, the empty-content check applies.\n- `<h1 aria-hidden>` / `aria-hidden=\"\"` / `aria-hidden={{\"\"}}` / `aria-hidden={{\" \"}}` → spec-default `undefined`, the empty-content check applies.\n\n## References\n\n- [WCAG SC 2.4.6 Headings and Labels](https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-descriptive.html)\n"
  },
  {
    "path": "docs/rules/template-no-extra-mut-helper-argument.md",
    "content": "# ember/template-no-extra-mut-helper-argument\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nDisallows passing more than one argument to the `mut` helper.\n\nA common mistake when using the Ember handlebars template `mut(attr)` helper is to pass an extra `value` parameter to it when only `attr` should be passed. Instead, the `value` should be passed outside of `mut`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n{{my-component click=(action (mut isClicked true))}}\n```\n\nThis rule **allows** the following:\n\n```hbs\n{{my-component click=(action (mut isClicked) true)}}\n```\n\n## Related Rules\n\n- [template-no-mut-helper](template-no-mut-helper.md)\n\n## References\n\n- See the [documentation](https://emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/mut?anchor=mut) for the Ember handlebars template `mut` helper\n"
  },
  {
    "path": "docs/rules/template-no-forbidden-elements.md",
    "content": "# ember/template-no-forbidden-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule disallows the use of forbidden elements in template files.\n\nThe rule is configurable so teams can add their own disallowed elements.\nThe default list of forbidden elements are `meta`, `style`, `html`, and `script`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><script></script></template>\n```\n\n```gjs\n<template><style></style></template>\n```\n\n```gjs\n<template><html></html></template>\n```\n\n```gjs\n<template><meta charset='utf-8' /></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><header></header></template>\n```\n\n```gjs\n<template><div></div></template>\n```\n\n```gjs\n<template>\n  <head>\n    <meta charset='utf-8' />\n  </head>\n</template>\n```\n\nNote: `<meta>` inside `<head>` is allowed as an exception.\n\n## Configuration\n\n- `boolean` — `true` to enable with defaults / `false` to disable\n- `string[]` — an array of element names to forbid (default: `['meta', 'style', 'html', 'script']`)\n\n## References\n\n- [Ember guides/template restrictions](https://guides.emberjs.com/release/components/#toc_restrictions)\n"
  },
  {
    "path": "docs/rules/template-no-heading-inside-button.md",
    "content": "# ember/template-no-heading-inside-button\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nAssistive technology allows users to browse a page by heading elements (`<h1>` - `<h6>`). However, if those heading elements are nested inside of button elements, they will automatically be marked as presentational by browsers. Any HTML element where [\"children presentational\" is true](https://w3c.github.io/aria/#button) should be coerced by the browser to be presentational, and therefore not included in the accessibility tree.\n\nAs such, nesting a heading element inside of a button element will cause failures for WCAG requirement 1.3.1, Info and Relationships, because the heading has lost semantic meaning.\n\nThis rule checks `<button>` elements to see if they contain heading (`<h1>` - `<h6>`) elements, and gives an error message if they are found.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><button><h1>Some Text</h1></button></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><button><span>Button Text</span></button></template>\n```\n\n## Migration\n\n- Replace `<h1>` - `<h6>` elements inside of `<button>` elements with classes that reflect the desired styling.\n\n## References\n\n- <https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html>\n- <https://w3c.github.io/aria/#button>\n"
  },
  {
    "path": "docs/rules/template-no-html-comments.md",
    "content": "# ember/template-no-html-comments\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow HTML comments in templates. HTML comments will be visible in the rendered output, which may expose sensitive information or clutter the DOM.\n\n## Rule Details\n\nThis rule disallows HTML comments (`<!-- -->`) in templates and suggests using Glimmer comments (`{{! }}` or `{{!-- --}}`) instead.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <!-- This is an HTML comment -->\n  <div>Content</div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  {{! This is a Glimmer comment }}\n  <div>Content</div>\n</template>\n\n<template>\n  {{!-- This is a block Glimmer comment --}}\n  <div>Content</div>\n</template>\n```\n\n## References\n\n- [Ember guides/template features](https://guides.emberjs.com/release/components/#toc_supported-features)\n- [Handlebars docs/comments](https://handlebarsjs.com/guide/#template-comments)\n"
  },
  {
    "path": "docs/rules/template-no-implicit-this.md",
    "content": "# ember/template-no-implicit-this\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nRequire explicit `this` for property access in templates to avoid ambiguity.\n\nThis rule aides in the migration path for [emberjs/rfcs#308](https://github.com/emberjs/rfcs/pull/308).\n\n## Motivation\n\nCurrently, the way to access properties on a components class is `{{greeting}}`\nfrom a template. This works because the component class is one of the objects\nwe resolve against during the evaluation of the expression.\n\nThe first problem with this approach is that the `{{greeting}}` syntax is\nambiguous, as it could be referring to a local variable (block param), a helper\nwith no arguments, a closed over component, or a property on the component\nclass.\n\nConsider the following example where the ambiguity can cause issues:\n\nYou have a component class that looks like the following component and template:\n\n```js\nimport Component from '@ember/component';\nimport computed from '@ember/computed';\n\nexport default Component.extend({\n  formatName: computed('firstName', 'lastName', function () {\n    return `${this.firstName} ${this.lastName}`;\n  }),\n});\n```\n\n```hbs\n<h1>Hello {{formatName}}!</h1>\n```\n\nGiven `{ firstName: 'Chad', lastName: 'Hietala' }`, Ember will render the\nfollowing:\n\n```html\n<h1>Hello Chad Hietala!</h1>\n```\n\nNow some time goes on and someone adds a `formatName` helper at\n`app/helpers/formatName.js` that looks like the following:\n\n```js\nexport default function formatName([firstName, lastName]) {\n  return `${firstName} ${lastName}`;\n}\n```\n\nDue to the fact that helpers take precedence over property lookups, our\n`{{formatName}}` now resolves to a helper. When the helper runs it doesn't have\nany arguments so our template now renders the following:\n\n```html\n<h1>Hello !</h1>\n```\n\nThis can be a refactoring hazard and can often lead to confusion for readers of\nthe template. Upon encountering `{{greeting}}` in a component's template, the\nreader has to check all of these places: first, you need to scan the\nsurrounding lines for block params with that name; next, you check in the\nhelpers folder to see if there is a helper with that name (it could also be\ncoming from an addon!); finally, you check the component's JavaScript class to\nlook for a (computed) property.\n\nLike\n[RFC#0276](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md)\nmade argument usage explicit through the `@` prefix, the `this` prefix will\nresolve the ambiguity and greatly improve clarity, especially in big projects\nwith a lot of files (and uses a lot of addons).\n\nAs an aside, the ambiguity that causes confusion for human readers is also a\nproblem for the compiler. While it is not the main goal of this proposal,\nresolving this ambiguity also helps the rendering system. Currently, the\n\"runtime\" template compiler has to perform a helper lookup for every\n`{{greeting}}` in each template. It will be able to skip this resolution\nprocess and perform other optimizations (such as reusing the internal\n[reference](https://github.com/glimmerjs/glimmer-vm/blob/master/guides/04-references.md)\nobject and caches) with this addition.\n\nFurthermore, by enforcing the `this` prefix, tooling like the [Ember Language\nServer](https://github.com/emberwatch/ember-language-server) does not need to\nknow about fallback resolution rules. This makes common features like [\"Go To\nDefinition\"](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)\nmuch easier to implement since we have semantics that mean \"property on class\".\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```hbs\n{{property}}\n{{someValue}}\n```\n\nExamples of **correct** code for this rule:\n\n```hbs\n{{this.property}}\n{{@namedArg}}\n{{yield}}\n{{if this.condition 'yes' 'no'}}\n```\n\n## Options\n\n- object -- An object with the following keys:\n  - `allow` -- An array of string names to allow as implicit this. Paths that exactly match an entry in this list will not be flagged.\n\nExample:\n\n```json\n{\n  \"ember/template-no-implicit-this\": [\n    \"error\",\n    {\n      \"allow\": [\"book-details\"]\n    }\n  ]\n}\n```\n\n## Migration\n\n- use [ember-no-implicit-this-codemod](https://github.com/ember-codemods/ember-no-implicit-this-codemod)\n- [upgrade to Glimmer components](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/), which don't allow ambiguous access\n  - classic components have [auto-reflection](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md#motivation), and can use `this.myArgName` or `this.args.myArgNme` or `@myArgName` interchangeably\n\n## References\n\n- [Glimmer components](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/)\n- [RFC#0276 - Named Args](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md#motivation)\n- [RFC#308 - Deprecate implicit this](https://github.com/emberjs/rfcs/blob/master/text/0308-deprecate-property-lookup-fallback.md)\n- [Ember Octane Guide - Templates](https://guides.emberjs.com/release/components/component-state-and-actions/)\n"
  },
  {
    "path": "docs/rules/template-no-index-component-invocation.md",
    "content": "# ember/template-no-index-component-invocation\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows invoking components using an explicit `/index` or `::Index` suffix.\n\nComponents and Component Templates can be structured as `app/components/foo-bar/index.js` and\n`app/components/foo-bar/index.hbs`. This allows additional files related to the\ncomponent (such as a `README.md` file) to be co-located on the filesystem.\n\nFor template-only components, they can be either `app/components/foo-bar.hbs`\nor `app/components/foo-bar/index.hbs` without a corresponding JavaScript file.\n\nSimilarly, for addons, templates can be placed inside `addon/components` with\nthe same rules laid out above.\n\nIn all of these case, if a template file is present in `app/components` or\n`addon/components`, it will take precedence over any corresponding template\nfiles in `app/templates`, the `layout` property on classic components, or a\ntemplate with the same name that is made available with the resolver API.\nInstead of being resolved at runtime, a template in `app/components` will be\nassociated with the component's JavaScript class at build time.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><Foo::Index /></template>\n```\n\n```gjs\n<template>{{component 'foo/index'}}</template>\n```\n\n```gjs\n<template>{{foo/index}}</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><Foo /></template>\n```\n\n```gjs\n<template>{{component 'foo'}}</template>\n```\n\n```gjs\n<template>{{foo}}</template>\n```\n\n## Migration\n\n- replace all `::Index>` to `>`\n- replace all `/index}}` to `}}`\n\n## References\n\n- [RFC #481](https://github.com/emberjs/rfcs/blob/master/text/0481-component-templates-co-location.md#high-level-design)\n"
  },
  {
    "path": "docs/rules/template-no-inline-event-handlers.md",
    "content": "# ember/template-no-inline-event-handlers\n\n<!-- end auto-generated rule header -->\n\nDisallows DOM event handler attributes in templates.\n\nInline event handlers like `onclick=\"...\"` are an older pattern that should be replaced with the `{{on}}` modifier for better Ember integration and testability.\n\n## Rule Details\n\nThis rule disallows the use of inline DOM event handler attributes like `onclick`, `onsubmit`, etc.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <button onclick=\"alert('test')\">Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <div onmousedown=\"handleEvent()\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <form onsubmit=\"return false;\">Form</form>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <button {{on \"click\" this.handleClick}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <input {{on \"input\" this.handleInput}} />\n</template>\n```\n\n```gjs\n<template>\n  <form {{on \"submit\" this.handleSubmit}}>Form</form>\n</template>\n```\n\n## Migration\n\nReplace:\n\n```gjs\n<button onclick=\"alert('clicked')\">\n```\n\nWith:\n\n```gjs\n<button {{on \"click\" this.handleClick}}>\n```\n\n## References\n\n- [Ember.js Guides - Event Handling](https://guides.emberjs.com/release/components/component-state-and-actions/)\n- [eslint-plugin-ember template-no-inline-styles](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-inline-styles.md)\n"
  },
  {
    "path": "docs/rules/template-no-inline-linkto.md",
    "content": "# ember/template-no-inline-linkto\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows inline form of the LinkTo component.\n\n## Rule Details\n\nThe inline form of `<LinkTo>` (self-closing without content) should be avoided. Use the block form instead to provide link text.\n\nThis rule also disallows the curly `{{link-to}}` inline form (e.g., `{{link-to \"text\" \"route\"}}`). The block form `{{#link-to}}...{{/link-to}}` or `<LinkTo>` angle bracket syntax should be used instead.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <LinkTo @route=\"index\" />\n</template>\n```\n\n```gjs\n<template>\n  <LinkTo @route=\"about\"></LinkTo>\n</template>\n```\n\n```gjs\n<template>\n  {{link-to \"Link text\" \"routeName\"}}\n</template>\n```\n\n```gjs\n<template>\n  {{link-to \"Link text\" \"routeName\" prop1 prop2}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <LinkTo @route=\"index\">Home</LinkTo>\n</template>\n```\n\n```gjs\n<template>\n  <LinkTo @route=\"about\">\n    About Us\n  </LinkTo>\n</template>\n```\n\n```gjs\n<template>\n  {{#link-to \"routeName\" prop1 prop2}}Link text{{/link-to}}\n</template>\n```\n\n```gjs\n// User-authored `<LinkTo>` (not from `@ember/routing`) is not flagged in\n// strict mode, even when childless.\nimport LinkTo from './my-link-to-component';\n<template>\n  <LinkTo />\n</template>\n```\n\n## Strict-mode behavior\n\nIn `.gjs`/`.gts` strict mode, `<LinkTo>` only refers to Ember's router link when explicitly imported from `@ember/routing` (this also covers renamed imports such as `import { LinkTo as Link } from '@ember/routing'`). Without that import, `<LinkTo>` is treated as a user-authored component and the rule does not fire. The curly `{{link-to ...}}` form is unreachable in strict mode (`link-to` cannot be a JS identifier) and the autofix is skipped there.\n\n## References\n\n- [eslint-plugin-ember template-no-inline-link-to](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-inline-link-to.md)\n"
  },
  {
    "path": "docs/rules/template-no-inline-styles.md",
    "content": "# ember/template-no-inline-styles\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nInline styles are not the best practice because they are hard to maintain and usually make the overall size of the project bigger. This rule forbids inline styles. Use CSS classes instead.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><div style='width:900px'></div></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><div class='wide-element'></div></template>\n```\n\n```gjs\n<template>\n  {{! allowed when `allowDynamicStyles` is enabled  }}\n  <div style={{html-safe (concat 'background-image: url(' url ')')}}></div>\n</template>\n```\n\n## Options\n\n| Name                 | Type      | Default | Description                                                                           |\n| -------------------- | --------- | ------- | ------------------------------------------------------------------------------------- |\n| `allowDynamicStyles` | `boolean` | `true`  | When `true`, allows dynamic style values (e.g. `style={{...}}` or `style=\"{{...}}\"`). |\n\n## Related Rules\n\n- [style-concatenation](style-concatenation.md)\n\n## References\n\n- [Deprecations/binding style attributes](https://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes)\n"
  },
  {
    "path": "docs/rules/template-no-input-block.md",
    "content": "# ember/template-no-input-block\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nUse of the block form of the handlebars `input` helper will result in an error at runtime.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n{{#input}}Some Content{{/input}}\n```\n\nThis rule **allows** the following:\n\n```hbs\n{{input type='text' value=this.firstName disabled=this.entryNotAllowed size='50'}}\n```\n\n## References\n\n- [Ember api/input component](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input)\n- [rfcs/built in components](https://emberjs.github.io/rfcs/0459-angle-bracket-built-in-components.html)\n"
  },
  {
    "path": "docs/rules/template-no-input-tagname.md",
    "content": "# ember/template-no-input-tagname\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n`{{input tagName=x}}` will result in obtuse errors. Typically, the input will simply fail to render, whether used in block form or inline. The only valid `tagName` for the input helper is `input`. For `textarea`, `button`, and other input-like elements, you should instead create a new component or better use the DOM!\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{input tagName='foo'}}\n  {{input tagName=X}}\n  {{component 'input' tagName='foo'}}\n  {{component 'input' tagName=X}}\n  {{yield (component 'input' tagName='foo')}}\n  {{yield (component 'input' tagName=X)}}\n</template>\n```\n\n## Related rules\n\n- [no-link-to-tagname](no-link-to-tagname.md)\n- [no-unknown-arguments-for-builtin-components](no-unknown-arguments-for-builtin-components.md)\n\n## References\n\n- [Ember api/input component](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input)\n- [rfcs/built in components](https://emberjs.github.io/rfcs/0459-angle-bracket-built-in-components.html)\n"
  },
  {
    "path": "docs/rules/template-no-invalid-aria-attributes.md",
    "content": "# ember/template-no-invalid-aria-attributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow invalid ARIA attributes. Only use valid ARIA attributes as defined in the ARIA specification.\n\n## Rule Details\n\nThis rule validates that only standard ARIA attributes are used on elements.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div aria-fake=\"value\">Content</div>\n</template>\n\n<template>\n  <div aria-invalid-attr=\"value\">Content</div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div aria-label=\"Label\">Content</div>\n</template>\n\n<template>\n  <div aria-hidden=\"true\">Content</div>\n</template>\n\n<template>\n  <div aria-describedby=\"description-id\">Content</div>\n</template>\n```\n\n## References\n\n- [Using ARIA, Roles, States, and Properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques)\n"
  },
  {
    "path": "docs/rules/template-no-invalid-interactive.md",
    "content": "# ember/template-no-invalid-interactive\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n> Disallow non-interactive elements with interactive handlers\n\n## Rule Details\n\nThis rule prevents adding interactive event handlers (like `onclick`, `onkeydown`, etc.) to non-interactive HTML elements without proper ARIA roles.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div onclick={{this.handleClick}}>Click me</div>\n</template>\n```\n\n```gjs\n<template>\n  <span onkeydown={{this.handleKey}}>Press key</span>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <button onclick={{this.handleClick}}>Click me</button>\n</template>\n```\n\n```gjs\n<template>\n  <div role=\"button\" onclick={{this.handleClick}}>Click me</div>\n</template>\n```\n\n```gjs\n<template>\n  <button {{on \"click\" this.handleClick}}>Click me</button>\n</template>\n```\n\n## Options\n\n| Name                        | Type       | Default | Description                                                 |\n| --------------------------- | ---------- | ------- | ----------------------------------------------------------- |\n| `additionalInteractiveTags` | `string[]` | `[]`    | Extra tag names to treat as interactive.                    |\n| `ignoredTags`               | `string[]` | `[]`    | Tag names to skip checking.                                 |\n| `ignoreTabindex`            | `boolean`  | `false` | If `true`, `tabindex` does not make an element interactive. |\n| `ignoreUsemap`              | `boolean`  | `false` | If `true`, `usemap` does not make an element interactive.   |\n\n## References\n\n- [WCAG 2.1 - 2.1.1 Keyboard](https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html)\n- [eslint-plugin-ember template-no-invalid-interactive](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-invalid-interactive.md)\n"
  },
  {
    "path": "docs/rules/template-no-invalid-link-text.md",
    "content": "# ember/template-no-invalid-link-text\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows invalid or uninformative link text content.\n\nLink text should be descriptive and provide context about the destination. Generic phrases like \"click here\" or \"read more\" are not accessible because they don't convey meaningful information, especially for screen reader users who may navigate by links alone.\n\n## Rule Details\n\nThis rule disallows the following link text values:\n\n- \"click here\"\n- \"more info\"\n- \"read more\"\n- \"more\"\n\nComparison is case-insensitive and whitespace is normalized.\n\nLinks with a valid `aria-label` or `aria-labelledby` attribute are exempt. A valid `aria-label` must be non-empty and must not itself be a disallowed text value.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <a href=\"/about\">Click here</a>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"/docs\">Read more</a>\n</template>\n```\n\n```gjs\n<template>\n  <LinkTo @route=\"info\">More info</LinkTo>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <a href=\"/about\">About Us</a>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"/docs\">Documentation</a>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"/page\" aria-label=\"View user profile\">Click here</a>\n</template>\n```\n\n## Options\n\n| Name              | Type       | Default | Description                                                                 |\n| ----------------- | ---------- | ------- | --------------------------------------------------------------------------- |\n| `allowEmptyLinks` | `boolean`  | `false` | When `true`, allows links with no text content.                             |\n| `linkComponents`  | `string[]` | `[]`    | Additional component names treated as links (besides `<a>` and `<LinkTo>`). |\n\n## References\n\n- [WebAIM: Link Text and Appearance](https://webaim.org/techniques/hypertext/link_text)\n- [WCAG 2.4.4: Link Purpose (In Context)](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html)\n- [ember-template-lint: no-invalid-link-text](https://github.com/ember-template-lint/ember-template-lint/blob/main/docs/rule/no-invalid-link-text.md)\n"
  },
  {
    "path": "docs/rules/template-no-invalid-link-title.md",
    "content": "# ember/template-no-invalid-link-title\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow invalid `title` attributes on link elements. The title should not be empty or the same as the link text.\n\n## Rule Details\n\nThis rule ensures that link titles provide additional context and are not redundant with the link text.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <a href=\"/page\" title=\"\">Page</a>\n</template>\n\n<template>\n  <a href=\"/page\" title=\"Page\">Page</a>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <a href=\"/page\" title=\"More information about page\">Page</a>\n</template>\n\n<template>\n  <a href=\"/page\">Page</a>\n</template>\n```\n\n## Migration\n\n- If the `title` attribute value is the same as or part of the link text, it's better to leave it out.\n\n## References\n\n- [Supplementing link text with the title attribute](https://www.w3.org/TR/WCAG20-TECHS/H33.html)\n- [Understanding Link Purpose](https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-refs.html)\n"
  },
  {
    "path": "docs/rules/template-no-invalid-meta.md",
    "content": "# ember/template-no-invalid-meta\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow invalid meta tags.\n\nMeta tags must be well-formed and follow accessibility/usability best practices.\n\n## Rule Details\n\nThis rule checks `<meta>` elements for the following issues:\n\n1. **Invalid charset** — `charset` must be `utf-8` (case-insensitive).\n2. **Missing `content`** — If `name`, `property`, `itemprop`, or `http-equiv` is present, a `content` attribute is required.\n3. **Missing identifier** — If `content` is present, one of `name`, `property`, `itemprop`, `http-equiv`, or `charset` must also be present.\n4. **`http-equiv=\"refresh\"` redirect delay** — A meta refresh that redirects (contains `;`) must have a delay of `0`.\n5. **`http-equiv=\"refresh\"` plain delay** — A meta refresh without a redirect must have a delay greater than 72000 seconds.\n6. **Viewport `user-scalable=no`** — Disabling user scaling harms accessibility.\n7. **Viewport `maximum-scale`** — Setting a maximum scale restricts zooming.\n\n## Redirects & Refresh\n\nSometimes a page automatically redirects to a different page. When this happens after a timed delay, it is an unexpected change of context that may interrupt the user. Redirects without timed delays are okay, but please consider a server-side method for redirecting instead (method will vary based on your server type).\n\n## Orientation Lock\n\nWhen content is presented with a restriction to a specific orientation, users must orient their devices to view the content in the orientation that the author imposed. Some users have their devices mounted in a fixed orientation (e.g. on the arm of a power wheelchair), and if the content cannot be viewed in that orientation it creates problems for the user.\n\n## Examples\n\n### Incorrect\n\n```gjs\n<template>\n  <meta charset=\"iso-8859-1\" />\n</template>\n```\n\n```gjs\n<template>\n  <meta name=\"description\" />\n</template>\n```\n\nMissing `content` when `name` is present.\n\n```gjs\n<template>\n  <meta content=\"some value\" />\n</template>\n```\n\nMissing identifier (`name`, `property`, `itemprop`, or `http-equiv`) when `content` is present.\n\n```gjs\n<template>\n  <meta http-equiv=\"refresh\" content=\"5;url=https://example.com\" />\n</template>\n```\n\nRedirect delay must be 0.\n\n```gjs\n<template>\n  <meta http-equiv=\"refresh\" content=\"30\" />\n</template>\n```\n\nPlain refresh delay must be greater than 72000.\n\n```gjs\n<template>\n  <meta name=\"viewport\" content=\"width=device-width, user-scalable=no\" />\n</template>\n```\n\n```gjs\n<template>\n  <meta name=\"viewport\" content=\"width=device-width, maximum-scale=1\" />\n</template>\n```\n\n### Correct\n\n```gjs\n<template>\n  <meta charset=\"utf-8\" />\n</template>\n```\n\n```gjs\n<template>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n</template>\n```\n\n```gjs\n<template>\n  <meta name=\"description\" content=\"A description of the page\" />\n</template>\n```\n\n```gjs\n<template>\n  <meta http-equiv=\"refresh\" content=\"0;url=https://example.com\" />\n</template>\n```\n\n## Migration\n\n- To fix, reduce the timed delay to zero, or use the appropriate server-side redirect method for your server type.\n- To fix orientation issues, remove references to `maximum-scale=1.0` and change `user-scalable=no` to `user-scalable=yes`.\n\n## References\n\n- [MDN - Meta charset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset)\n- [WCAG - Meta Refresh](https://www.w3.org/TR/WCAG21/Understanding/timing-adjustable.html)\n"
  },
  {
    "path": "docs/rules/template-no-invalid-role.md",
    "content": "# ember/template-no-invalid-role\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows invalid ARIA roles in templates.\n\nARIA roles must be valid according to the ARIA specification. Using invalid roles can confuse assistive technologies and reduce accessibility.\n\n## Rule Details\n\nThis rule checks that all `role` attributes contain valid ARIA role values. It also disallows `role=\"presentation\"` and `role=\"none\"` on semantic HTML elements, as doing so strips meaning from elements that inherently convey information.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div role=\"invalid\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div role=\"btn\">Should be \"button\"</div>\n</template>\n```\n\n```gjs\n<template>\n  <button role=\"presentation\">Content</button>\n</template>\n```\n\n```gjs\n<template>\n  <nav role=\"none\">Navigation</nav>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div role=\"button\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div role=\"navigation\">Nav</div>\n</template>\n```\n\n```gjs\n<template>\n  <div role=\"presentation\">Decorative</div>\n</template>\n```\n\n```gjs\n<template>\n  <div>No role attribute</div>\n</template>\n```\n\n## Migration\n\n- If violations are found, remediation should be planned to replace the semantic HTML with the `div` element. Additional CSS will likely be required.\n\n## Options\n\n| Name                    | Type      | Default | Description                                                   |\n| ----------------------- | --------- | ------- | ------------------------------------------------------------- |\n| `catchNonexistentRoles` | `boolean` | `true`  | When `true`, reports roles that don't exist in the ARIA spec. |\n\n## References\n\n- [eslint-plugin-ember template-no-invalid-role](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-invalid-role.md)\n- [WAI-ARIA Roles](https://www.w3.org/TR/wai-aria-1.2/#role_definitions)\n"
  },
  {
    "path": "docs/rules/template-no-jsx-attributes.md",
    "content": "# ember/template-no-jsx-attributes\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows JSX-style camelCase attributes in templates.\n\nFolks coming from React may have developed habits around how they type attributes on elements.\nJSX isn't HTML (it's JS), so in JS, you can't have kebab-case identifiers, so JSX uses camelCase.\n\nHowever, since Ember uses HTML, camelCase attributes are not valid when writing components.\n\n## Examples\n\nThis rule **forbids** the following attributes:\n\n- acceptCharset\n- accessKey\n- allowFullScreen\n- allowTransparency\n- autoComplete\n- autoFocus\n- autoPlay\n- cellPadding\n- cellSpacing\n- charSet\n- className\n- contentEditable\n- contextMenu\n- crossOrigin\n- dataTime\n- encType\n- formAction\n- formEncType\n- formMethod\n- formNoValidate\n- formTarget\n- frameBorder\n- httpEquiv\n- inputMode\n- keyParams\n- keyType\n- noValidate\n- marginHeight\n- marginWidth\n- maxLength\n- mediaGroup\n- minLength\n- radioGroup\n- readOnly\n- rowSpan\n- spellCheck\n- srcDoc\n- srcSet\n- tabIndex\n- useMap\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div className='foo'></div>\n  <div contentEditable='true'></div>\n  <img srcSet='image.jpg 1x, image@2x.jpg 2x' />\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <div class='foo'></div>\n  <div contenteditable='true'></div>\n  <img srcset='image.jpg 1x, image@2x.jpg 2x' />\n</template>\n```\n\n## Migration\n\nConvert attributes to kebab-case[^camelCaseNote]\n\n- `<div className=\"...\">` -> `<div class=\"...\">`\n- `<video autoPlay>` -> `<video auto-play>`\n- `<div contentEditable>` -> `<div content-editable>`\n- etc\n\n[^camelCaseNote]: keep in mind that `@args`, and `<:blocks>` should be js-compatible identifiers and be camelCase\n\n## References\n\n- [HTML Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes)\n- [React JSX differences](https://reactjs.org/docs/dom-elements.html#differences-in-attributes)\n"
  },
  {
    "path": "docs/rules/template-no-let-reference.md",
    "content": "# ember/template-no-let-reference\n\n💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): ![gjs logo](/docs/svgs/gjs.svg) `recommended-gjs`, ![gts logo](/docs/svgs/gts.svg) `recommended-gts`.\n\n<!-- end auto-generated rule header -->\n\nDisallows referencing let/var variables in templates.\n\n```js\n// app/components/foo.gjs\nlet foo = 1;\n\nfunction increment() {\n  foo++;\n}\n\nexport default <template>{{ foo }}</template>;\n```\n\nThis does not \"work\" – it doesn't error, but it will essentially capture and compile in the value of the foo variable at some arbitrary point and never update again. Even if the component is torn down and a new instance is created/rendered, it will probably still hold on to the old value when the template was initially compiled.\n\nSo, generally speaking, one should avoid referencing let variables from within &lt;template&gt; and instead prefer to use const bindings.\n\n## Rule Detail\n\nUse `const` variables instead of `let`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nlet x = 1;\n<template>{{ x }}</template>;\n```\n\n```js\nlet Comp = x; // SomeComponent\n<template>\n  <Comp />\n</template>;\n```\n\nExamples of **correct** code for this rule:\n\n```js\nconst x = 1;\n<template>{{ x }}</template>;\n```\n\n```js\nconst Comp = x; // SomeComponent\n<template>\n  <Comp />\n</template>;\n```\n"
  },
  {
    "path": "docs/rules/template-no-link-to-positional-params.md",
    "content": "# ember/template-no-link-to-positional-params\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n> Disallow positional params in LinkTo component\n\n## Rule Details\n\nPositional parameters in `<LinkTo>` components are deprecated. Use named arguments instead.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{! Old positional params style }}\n  <LinkTo \"posts.post\" @model={{this.post}}>Post</LinkTo>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <LinkTo @route=\"posts.post\" @model={{this.post}}>Post</LinkTo>\n</template>\n```\n\n```gjs\n<template>\n  <LinkTo @route=\"index\">Home</LinkTo>\n</template>\n```\n\n## References\n\n- [eslint-plugin-ember template-no-link-to-positional-params](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-link-to-positional-params.md)\n"
  },
  {
    "path": "docs/rules/template-no-link-to-tagname.md",
    "content": "# ember/template-no-link-to-tagname\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n> Disallow tagName attribute on LinkTo component\n\n## Rule Details\n\nThe `tagName` attribute on `<LinkTo>` components is deprecated. Use the appropriate HTML element or component instead.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <LinkTo @route=\"index\" tagName=\"button\">Home</LinkTo>\n</template>\n```\n\n```gjs\n<template>\n  <LinkTo @route=\"about\" @tagName=\"span\">About</LinkTo>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <LinkTo @route=\"index\">Home</LinkTo>\n</template>\n```\n\n```gjs\n<template>\n  <button type=\"button\" {{on \"click\" (fn this.transitionTo \"index\")}}>Home</button>\n</template>\n```\n\n## Migration\n\n- Remove the `tagName` overrides and, if you need it, adjust the styling of the\n  `<a>` elements to make them look like buttons\n\n## Related rules\n\n- [no-input-tagname](template-no-input-tagname.md)\n- [no-unknown-arguments-for-builtin-components](template-no-unknown-arguments-for-builtin-components.md)\n\n## References\n\n- [eslint-plugin-ember template-no-link-to-tagname](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-link-to-tagname.md)\n"
  },
  {
    "path": "docs/rules/template-no-log.md",
    "content": "# ember/template-no-log\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows usage of `{{log}}` in templates.\n\nThe `{{log}}` helper is useful for debugging but should not be present in production code. Use proper logging libraries or console statements in JavaScript code instead.\n\n## Rule Details\n\nThis rule disallows the use of `{{log}}` statements in templates.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{log \"debug message\"}}\n  <div>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  {{#if condition}}\n    {{log this.value}}\n  {{/if}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  {{this.log}}\n</template>\n```\n\n```gjs\n<template>\n  {{logger \"info\"}}\n</template>\n```\n\n## Related Rules\n\n- [no-console](https://eslint.org/docs/rules/no-console) from ESLint\n\n## References\n\n- [ember-template-lint no-log](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-log.md)\n"
  },
  {
    "path": "docs/rules/template-no-model-argument-in-route-templates.md",
    "content": "# ember/template-no-model-argument-in-route-templates\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nIn Ember route templates, the model should be accessed via `this.model` in the controller or component, not as an `@model` argument. The `@model` argument pattern is more appropriate for components. This rule primarily targets `.hbs` files in the `templates/` directory.\n\n## Rule Details\n\nThis rule disallows the use of `@model` argument in route templates (`.hbs` files in `templates/` directory).\n\n## Examples\n\nExamples of **incorrect** code for this rule (in route templates):\n\n```hbs\n<!-- app/templates/index.hbs -->\n{{@model}}\n```\n\n```hbs\n<!-- app/templates/users.hbs -->\n{{@model.name}}\n```\n\n```hbs\n<!-- app/templates/posts/show.hbs -->\n{{@model.id}}\n```\n\nExamples of **correct** code for this rule:\n\n```hbs\n<!-- app/templates/index.hbs -->\n{{this.model}}\n```\n\n```hbs\n// app/components/user-card.gjs\n{{@model.name}}\n```\n\n```hbs\n{{this.model}}\n```\n\n## Migration\n\nThis rule includes a fixer in order to handle the migration for you automatically.\n\n## References\n\n- [eslint-plugin-ember template-no-model-argument-in-route-templates](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-model-argument-in-route-templates.md)\n- [Ember Guides: Controllers](https://guides.emberjs.com/release/routing/controllers/)\n"
  },
  {
    "path": "docs/rules/template-no-multiple-empty-lines.md",
    "content": "# ember/template-no-multiple-empty-lines\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows multiple consecutive empty lines in templates.\n\nMultiple consecutive blank lines reduce readability and should be limited.\n\n## Rule Details\n\nThis rule enforces a maximum number of consecutive empty lines (default: 1).\n\n## Configuration\n\nThe following values are valid configuration:\n\n- object -- An object with the following keys:\n  - `max` -- An integer specifying the maximum number of consecutive empty lines allowed. Defaults to `1`.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div>First</div>\n\n\n  <div>Second</div>\n</template>\n```\n\n```gjs\n<template>\n  <div>Content</div>\n\n\n\n  <div>More content</div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>First</div>\n\n  <div>Second</div>\n</template>\n```\n\n```gjs\n<template>\n  <div>Content</div>\n  <div>More content</div>\n</template>\n```\n\n## References\n\n- [eslint-plugin-ember template-no-multiple-empty-lines](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-multiple-empty-lines.md)\n- [ESLint no-multiple-empty-lines](https://eslint.org/docs/rules/no-multiple-empty-lines)\n"
  },
  {
    "path": "docs/rules/template-no-mut-helper.md",
    "content": "# ember/template-no-mut-helper\n\n<!-- end auto-generated rule header -->\n\nDisallow usage of the `(mut)` helper.\n\nThe `(mut)` helper was used in classic Ember to create two-way bindings. In modern Ember (Octane and beyond), this pattern is discouraged in favor of explicit one-way data flow with actions or setters.\n\n## Rule Details\n\nThis rule disallows using the `(mut)` helper in templates.\n\n## Reasons to not use [the `mut` helper](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/each?anchor=mut)\n\n1. General problems in the programming model:\n   - The mut helper is non-intuitive to use, see, teach, and learn since it can either be a getter or a setter based on the context in which it’s used.\n\n   Example:\n\n   ```hbs\n   {{#let (mut this.foo) as |foo|}}\n     <!-- When used like this, it's a getter -->\n     {{foo}}\n\n     <!-- When used like this, it's a setter -->\n     <button {{action foo 123}}>Update Foo</button>\n   {{/let}}\n   ```\n\n   - The need for the [no-extra-mut-helper-argument](template-no-extra-mut-helper-argument.md) rule is further evidence that `mut` has a non-intuitive signature and frequently gets misused.\n   - The mut helper is usually only used as a pure setter, in which case there are other template helpers that are pure setters that could be used instead of mut (e.g. [ember-set-helper](https://github.com/pzuraq/ember-set-helper)).\n\n2. Incompatibility with Glimmer Component intentions:\n   - The mut helper can re-introduce 2 way data binding into Glimmer Components on named arguments where a child can change a parent’s data, which goes against the Data Down Actions Up principle, goes against Glimmer Components’ intention to have immutable arguments, and is [discouraged by the Ember Core team](https://www.pzuraq.com/blog/on-mut-and-2-way-binding/).\n\nExample:\n\n```hbs\n<input type='checkbox' checked={{@checked}} {{on 'change' (fn (mut @checked) (not @checked))}} />\n```\n\n## What this rule does\n\nThis rule forbids any use of the `mut` helper, both as a getter and a setter, in any context. It also\nsurfaces possible alternatives in the lint violation message to help guide engineers to resolving\nthe lint violations.\n\n## Examples\n\n### Incorrect ❌\n\n```hbs\n<Input @value={{this.name}} @onChange={{mut this.name}} />\n```\n\n```hbs\n{{input value=(mut this.name)}}\n```\n\n```hbs\n<CustomComponent @onChange={{mut this.value}} />\n```\n\n### Correct ✅\n\n```hbs\n<Input @value={{this.name}} @onChange={{this.updateName}} />\n```\n\n```hbs\n<Input @value={{this.name}} @onChange={{fn this.updateName}} />\n```\n\n```hbs\n<CustomComponent @onChange={{this.handleChange}} />\n```\n\n## Migration\n\n1. When used as a pure setter only, `mut` could be replaced by a JS action (\"Option 1\" below) or [ember-set-helper](https://github.com/pzuraq/ember-set-helper) (\"Option 2\" below):\n\nBefore:\n\n```hbs\n<MyComponent @closeDropdown={{action (mut this.setIsDropdownOpen) false}} />\n```\n\nAfter (Option 1 HBS):\n\n```hbs\n<MyComponent @closeDropdown={{action this.setIsDropdownOpen false}} />\n```\n\nAfter (Option 1 JS):\n\n```js\n// in your component class\nclass MyComponent extends Component {\n  @action\n  setIsDropdownOpen(isDropdownOpen) {\n    set(this, 'isDropdownOpen', isDropdownOpen);\n  }\n}\n```\n\nAfter (Option 2):\n\n```hbs\n<MyComponent @closeDropdown={{set this 'isDropdownOpen' false}} />\n```\n\n\\\n2. When used as a pure getter only, `mut` could be removed:\n\nBefore:\n\n```hbs\n<MyComponent @isDropdownOpen={{mut this.isDropdownOpen}} />\n```\n\nAfter:\n\n```hbs\n<MyComponent @isDropdownOpen={{this.isDropdownOpen}} />\n```\n\n\\\n3. When `mut` is used as a getter and setter, `mut` could be replaced with a different namespace for the property and a dedicated action function to set the property: (Note: another other option could be to pull in the pick helper from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers) and use it like [this](https://github.com/pzuraq/ember-set-helper#picking-values-with-ember-composable-helpers).) (Note: Another option could be to use [ember-box](https://github.com/pzuraq/ember-box)).\n\nBefore:\n\n```hbs\n{{#let (mut this.foo) as |foo|}}\n  {{foo}}\n  <input onchange={{action foo value=”target.value”}} />\n{{/let}}\n```\n\nAfter HBS:\n\n```hbs\n{{this.foo}}\n<input {{on “change” this.updateFoo}} />\n```\n\nAfter JS:\n\n```js\n// in your component class\nclass MyComponent extends Component {\n  @tracked\n  foo;\n\n  @action\n  updateFoo(evt) {\n    this.foo = evt.target.value;\n    // or set(this, 'foo', evt.target.value); for legacy Ember code\n  }\n}\n```\n\n\\\n4. When `mut` is being passed into a built-in classic component that uses 2 way data binding, `mut` could be removed:\n\nBefore:\n\n```hbs\n<Input @value={{mut this.profile.description}} />\n```\n\nAfter:\n\n```hbs\n<Input @value={{this.profile.description}} />\n```\n\n## Options\n\n| Name                | Type     | Default | Description                                                                   |\n| ------------------- | -------- | ------- | ----------------------------------------------------------------------------- |\n| `setterAlternative` | `string` |         | If provided, the error message suggests using this helper instead of `(mut)`. |\n\n## Related Rules\n\n- [no-mut-helper](./no-mut-helper.md)\n\n## References\n\n- [Ember Octane Guide - Two-way bindings](https://guides.emberjs.com/release/upgrading/current-edition/)\n- [eslint-plugin-ember template-no-mut-helper](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-mut-helper.md)\n"
  },
  {
    "path": "docs/rules/template-no-negated-condition.md",
    "content": "# ember/template-no-negated-condition\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow negated conditions in `{{#if}}` blocks. Use `{{#unless}}` instead or rewrite the condition.\n\n## Rule Details\n\nThis rule discourages the use of `{{#if (not condition)}}` in favor of `{{#unless condition}}` for better readability.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{#if (not isValid)}}\n    Invalid\n  {{/if}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  {{#unless isValid}}\n    Invalid\n  {{/unless}}\n</template>\n\n<template>\n  {{#if isValid}}\n    Valid\n  {{/if}}\n</template>\n```\n\n## Options\n\n| Name              | Type      | Default | Description                                                                                                             |\n| ----------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `simplifyHelpers` | `boolean` | `true`  | When `true`, also reports negated comparison helpers (e.g. `(not (eq ...))`) and suggests using `(not-eq ...)` instead. |\n\n## Related Rules\n\n- [simple-unless](template-simple-unless.md)\n\n## References\n\n- [no-negated-condition](https://eslint.org/docs/rules/no-negated-condition) from eslint\n"
  },
  {
    "path": "docs/rules/template-no-nested-interactive.md",
    "content": "# ember/template-no-nested-interactive\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows nested interactive elements in templates.\n\nNested interactive elements (like a button inside a link) are not accessible to keyboard and screen reader users. This creates confusion about which element is actually interactive and can cause unexpected behavior.\n\n## Rule Details\n\nThis rule disallows nesting interactive elements inside other interactive elements.\n\nInteractive elements include:\n\n- `<a>` (only when it has an `href` attribute)\n- `<audio>` (only when it has a `controls` attribute)\n- `<button>`\n- `<canvas>` (drawing/game-UI convention; not in the HTML spec's interactive-content category)\n- `<details>`\n- `<embed>`\n- `<iframe>`\n- `<input>` (except `type=\"hidden\"`)\n- `<label>`\n- `<select>`\n- `<summary>`\n- `<textarea>`\n- `<video>` (only when it has a `controls` attribute)\n- Elements with interactive ARIA roles (e.g., `role=\"button\"`, `role=\"link\"`)\n- Elements with `tabindex` (unless `ignoreTabindex` is enabled)\n- Elements with `contenteditable` (except `contenteditable=\"false\"`)\n- Elements with `usemap` (`<img>`, `<object>` only, unless `ignoreUsemap` is enabled)\n\nSpecial cases:\n\n- `<label>` may contain **one** interactive child (e.g., `<label><input /></label>` is fine, but `<label><input /><button>x</button></label>` is not)\n- `<summary>` as the first child of `<details>` is allowed; other interactive content after `<summary>` (in the disclosed panel) is also allowed\n- Canonical ARIA composite-widget hierarchies are allowed (e.g., `role=\"option\"` inside `role=\"listbox\"`, `role=\"tab\"` inside `role=\"tablist\"`, `role=\"row\"` inside `role=\"grid\"`, `role=\"radio\"` inside `role=\"radiogroup\"`). Derived from the ARIA `requiredOwnedElements` relationship.\n- Nested `role=\"menuitem\"` / `role=\"menuitemcheckbox\"` / `role=\"menuitemradio\"` elements are allowed (menu/sub-menu pattern)\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <button>\n    <a href=\"#\">Link inside button</a>\n  </button>\n</template>\n```\n\n```gjs\n<template>\n  <a href=\"#\">\n    <button>Button inside link</button>\n  </a>\n</template>\n```\n\n```gjs\n<template>\n  <label>\n    <input type=\"text\" />\n    <button>Submit</button>\n  </label>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>\n    <button>Button</button>\n    <a href=\"#\">Link</a>\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <label>\n    <input type=\"text\" />\n    Label text\n  </label>\n</template>\n```\n\n```gjs\n<template>\n  <details>\n    <summary>Toggle</summary>\n    Content here\n  </details>\n</template>\n```\n\n```gjs\n<template>\n  <a>Not interactive without href</a>\n</template>\n```\n\n## Options\n\n| Name                        | Type       | Default | Description                                                   |\n| --------------------------- | ---------- | ------- | ------------------------------------------------------------- |\n| `additionalInteractiveTags` | `string[]` | `[]`    | Extra tag names to consider interactive.                      |\n| `ignoredTags`               | `string[]` | `[]`    | Tag names to skip checking.                                   |\n| `ignoreTabindex`            | `boolean`  | `false` | If `true`, `tabindex` does not make an element interactive.   |\n| `ignoreUsemap`              | `boolean`  | `false` | If `true`, `usemap` does not make an element interactive.     |\n| `ignoreUsemapAttribute`     | `boolean`  | `false` | Alias for `ignoreUsemap` (original ember-template-lint name). |\n\n## References\n\n- [eslint-plugin-ember template-no-nested-interactive](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-nested-interactive.md)\n- [WCAG 2.1 - Interactive controls must not be nested](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html)\n"
  },
  {
    "path": "docs/rules/template-no-nested-landmark.md",
    "content": "# ember/template-no-nested-landmark\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows nesting landmark elements of the same type.\n\nLandmark elements should not be nested within other landmarks of the same name. This creates confusion for screen reader users navigating by landmarks.\n\n## Rule Details\n\nThis rule disallows nesting landmark elements or roles within other landmark elements or roles of the same type.\n\nLandmark elements include:\n\n- `<header>` (banner)\n- `<nav>` (navigation)\n- `<main>` (main)\n- `<aside>` (complementary)\n- `<footer>` (contentinfo)\n- `<section>` (region)\n- `<form>` (form)\n- Elements with landmark roles\n\n## List of elements & their corresponding roles\n\n- header (banner)\n- main (main)\n- aside (complementary)\n- form (form, search)\n- main (main)\n- nav (navigation)\n- footer (contentinfo)\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <nav>\n    <nav>Nested navigation</nav>\n  </nav>\n</template>\n```\n\n```gjs\n<template>\n  <main>\n    <main>Nested main</main>\n  </main>\n</template>\n```\n\n```gjs\n<template>\n  <div role=\"navigation\">\n    <nav>Nested nav</nav>\n  </div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <nav>Navigation</nav>\n  <main>Content</main>\n</template>\n```\n\n```gjs\n<template>\n  <main>\n    <nav>Navigation inside main</nav>\n  </main>\n</template>\n```\n\n```gjs\n<template>\n  <main>\n    <div>Regular content</div>\n  </main>\n</template>\n```\n\n## References\n\n- [eslint-plugin-ember template-no-nested-landmark](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-nested-landmark.md)\n- [WAI-ARIA Landmarks](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/)\n"
  },
  {
    "path": "docs/rules/template-no-nested-splattributes.md",
    "content": "# ember/template-no-nested-splattributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow nested `...attributes` usage.\n\nHaving `...attributes` on multiple elements nested within each other in a component can cause unintended results. This rule prevents `...attributes` on an element if any of its parent elements already has `...attributes`.\n\n## Rule Details\n\nThis rule disallows `...attributes` on an element when an ancestor element already has `...attributes`.\n\n## Examples\n\n### Incorrect ❌\n\n```gjs\n<template>\n  <div ...attributes>\n    <span ...attributes>Text</span>\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <section ...attributes>\n    <div>\n      <button ...attributes>Click</button>\n    </div>\n  </section>\n</template>\n```\n\n### Correct ✅\n\n```gjs\n<template>\n  <div ...attributes>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div ...attributes>\n    <span>Text</span>\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <div ...attributes>first</div>\n  <div ...attributes>second</div>\n</template>\n```\n\n## Migration\n\n- Remove the inner `...attributes` declaration\n\n## References\n\n- [Ember Guides - Splattributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/#toc_html-attributes)\n- [eslint-plugin-ember template-no-nested-splattributes](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-nested-splattributes.md)\n"
  },
  {
    "path": "docs/rules/template-no-obscure-array-access.md",
    "content": "# ember/template-no-obscure-array-access\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow obscure array access patterns in templates.\n\n## Rule Details\n\nThis rule discourages the use of obscure array access patterns in templates, including:\n\n- Numeric array index access like `{{list.[0]}}` or `{{list.[1].name}}`\n- `@each` property access like `{{items.@each.name}}`\n- `[]` property access like `{{items.[].property}}`\n\nUsing obscure expressions like `{{list.[1].name}}` is discouraged. This rule recommends the use of Ember's `get` helper as an alternative for accessing array values.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <Foo @bar={{@list.[0]}} />\n</template>\n```\n\n```gjs\n<template>\n  {{@list.[1].name}}\n</template>\n```\n\n```gjs\n<template>\n  {{items.@each.name}}\n</template>\n```\n\n```gjs\n<template>\n  {{items.[].property}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <Foo @bar={{get @list '0'}} />\n</template>\n```\n\n```gjs\n<template>\n  {{get @list '1.name'}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each items as |item|}}\n    {{item.name}}\n  {{/each}}\n</template>\n```\n\n## References\n\n- [eslint-plugin-ember template-no-obscure-array-access](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-obscure-array-access.md)\n"
  },
  {
    "path": "docs/rules/template-no-obsolete-elements.md",
    "content": "# ember/template-no-obsolete-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nSome elements are entirely obsolete and must not be used by authors.\n\nThis rule forbids the use of obsolete elements.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <acronym></acronym>\n  <applet></applet>\n  <basefont></basefont>\n  <bgsound></bgsound>\n  <big></big>\n  <blink></blink>\n  <center></center>\n  <dir></dir>\n  <font></font>\n  <frame></frame>\n  <frameset></frameset>\n  <isindex></isindex>\n  <keygen />\n  <listing></listing>\n  <marquee></marquee>\n  <menuitem></menuitem>\n  <multicol></multicol>\n  <nextid></nextid>\n  <nobr></nobr>\n  <noembed></noembed>\n  <noframes></noframes>\n  <param>\n  <plaintext></plaintext>\n  <rb></rb>\n  <rtc></rtc>\n  <spacer></spacer>\n  <strike></strike>\n  <tt></tt>\n  <xmp></xmp>\n</template>\n```\n\nThis rule **allows** anything that is not an obsolete element.\n\n## Migration\n\n- replace any use of these elements with the appropriate updated element or a `div` element.\n\n## References\n\n- [HTML non-conforming features](https://html.spec.whatwg.org/multipage/obsolete.html#non-conforming-features)\n- [HTML5 obsolete features](https://dev.w3.org/html5/spec-LC/obsolete.html)\n- [Failure of Success Criterion 2.2.2 due to using the blink element](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F47)\n"
  },
  {
    "path": "docs/rules/template-no-only-default-slot.md",
    "content": "# ember/template-no-only-default-slot\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallows using only the `default` slot when rendering content into a component.\n\nThe default slot (`<:default>`) is used to explicitly target the main content block of a component. However, when _only_ the default slot is used — with no named slots — the extra syntax is redundant and unnecessary.\n\nThis rule disallows using only the `default` slot block when rendering content into a component. The preferred form is to pass the content directly, without the default slot wrapper.\n\n## Motivation\n\nWhen a component has a single default block like this:\n\n```gjs\n<template>\n  <MyComponent>\n    <:default>\n      Hello!\n    </:default>\n  </MyComponent>\n</template>\n```\n\nThe `<:default>` adds no semantic value. It's simpler and clearer to write:\n\n```gjs\n<template>\n  <MyComponent>\n    Hello!\n  </MyComponent>\n</template>\n```\n\nExplicit slot naming should only be used when multiple slots are present, and disambiguation is needed.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <MyComponent>\n    <:default>\n      What?\n    </:default>\n  </MyComponent>\n</template>\n```\n\n```gjs\n<template>\n  <MyComponent>\n    <:default>\n      <p>Hello world</p>\n    </:default>\n  </MyComponent>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <MyComponent>\n    Hello!\n  </MyComponent>\n</template>\n```\n\n```gjs\n<template>\n  <MyComponent>\n    <:header>Header</:header>\n    <:default>Content</:default>\n  </MyComponent>\n</template>\n```\n\n## Migration\n\nIf you see this pattern:\n\n```hbs\n<SomeCard>\n  <:default>\n    Card Content\n  </:default>\n</SomeCard>\n```\n\nJust remove the `<:default>` wrapper:\n\n```hbs\n<SomeCard>\n  Card Content\n</SomeCard>\n```\n\n## References\n\n- [Ember Guides - Named Blocks](https://guides.emberjs.com/release/components/block-content/#toc_named-blocks)\n"
  },
  {
    "path": "docs/rules/template-no-outlet-outside-routes.md",
    "content": "# ember/template-no-outlet-outside-routes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallow `{{outlet}}` outside of route templates. The `outlet` helper should only be used in route templates to render nested routes.\n\n## Rule Details\n\nThis rule prevents the use of `{{outlet}}` in component templates where it doesn't make sense.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n// In a component template\n<template>\n  <div>\n    {{outlet}}\n  </div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n// In a component template\n<template>\n  <div>Content</div>\n</template>\n```\n\n## References\n\n- [Ember guides/routing](https://guides.emberjs.com/release/routing/rendering-a-template/)\n- [Ember api/outlet helper](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/outlet?anchor=outlet)\n- [ember-template-lint no-outlet-outside-routes](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-outlet-outside-routes.md)\n"
  },
  {
    "path": "docs/rules/template-no-page-title-component.md",
    "content": "# ember/template-no-page-title-component\n\n<!-- end auto-generated rule header -->\n\nDisallows usage of the `<PageTitle>` component.\n\n## Rule Details\n\nUse the `{{pageTitle}}` helper instead of the `<PageTitle>` component from ember-page-title.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <PageTitle>My Page</PageTitle>\n</template>\n```\n\n```gjs\n<template>\n  <PageTitle @title=\"My Page\" />\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  {{pageTitle \"My Page\"}}\n</template>\n```\n\n```gjs\n<template>\n  {{pageTitle this.dynamicTitle}}\n</template>\n```\n\n## References\n\n- [ember-page-title documentation](https://github.com/ember-cli/ember-page-title)\n"
  },
  {
    "path": "docs/rules/template-no-passed-in-event-handlers.md",
    "content": "# ember/template-no-passed-in-event-handlers\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nIt is possible to pass e.g. `@click` to an Ember component to override the default `click` event handler. For tagless components this will trigger an assertion though and can't be used as legitimate API, and for Glimmer components it will not work out of the box, like in Ember components, either.\n\nThis rule scans potential component invocations for these patterns and flags them as issues.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<Foo @click={{this.handleClick}} />\n```\n\n```hbs\n<Foo @keyPress={{this.handleClick}} />\n```\n\n```hbs\n{{foo click=this.handleClick}}\n```\n\nThis rule **allows** the following:\n\n```hbs\n<Foo @onClick={{this.handleClick}} />\n```\n\n```hbs\n<Foo @myCustomClickHandler={{this.handleClick}} />\n```\n\n```hbs\n<Foo @onKeyPress={{this.handleClick}} />\n```\n\n```hbs\n{{foo onClick=this.handleClick}}\n```\n\n## Configuration\n\n- boolean - `true` to enable / `false` to disable\n  - object -- An object with the following keys:\n    - `ignore` -- An object with the following keys/values:\n      - key: string -- The name of the element or mustache statement to ignore event handlers for\n      - value: array -- Event handler names. Event handler names should exclude the @ when specifying those intended for named arguments. This rule will ensure both non-named arguments and named arguments are both ignored appropriately.\n\n      eg.\n\n      Given the following configuration:\n\n      ```json\n      {\n        \"ignore\": {\n          \"MyButton\": [\"click\"]\n        }\n      }\n      ```\n\n## Migration\n\n- create explicit component APIs for these events (e.g. `@click` -> `@onClick`)\n\n## References\n\n- <https://api.emberjs.com/ember/release/classes/Component#event-handler-methods>\n"
  },
  {
    "path": "docs/rules/template-no-pointer-down-event-binding.md",
    "content": "# ember/template-no-pointer-down-event-binding\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows pointer down event bindings (`mousedown`, `pointerdown`).\n\nPointer down events fire before the user releases the pointer, which can cause accessibility issues — actions triggered on down events don't allow users to cancel by moving the pointer away before releasing. Bind to the corresponding pointer up event instead.\n\n## Rule Details\n\nThis rule disallows the use of `mousedown`, `onmousedown`, `pointerdown`, and `onpointerdown` events in templates, whether via `{{on}}`, `{{action on=...}}`, or HTML attributes.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <button {{on \"mousedown\" this.handleMouseDown}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <div {{on \"pointerdown\" this.handlePointerDown}}>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div onmousedown={{this.handleMouseDown}}>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div {{action this.handler on=\"mousedown\"}}></div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <button {{on \"mouseup\" this.handleMouseUp}}>Click</button>\n</template>\n```\n\n```gjs\n<template>\n  <div {{on \"pointerup\" this.handlePointerUp}}>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <button {{on \"click\" this.handleClick}}>Click</button>\n</template>\n```\n\n## Migration\n\nReplace:\n\n```gjs\n<button {{on \"mousedown\" this.action}}>\n```\n\nWith:\n\n```gjs\n<button {{on \"mouseup\" this.action}}>\n```\n\nOr use the more modern pointer event:\n\n```gjs\n<button {{on \"pointerup\" this.action}}>\n```\n\n## References\n\n- [ember-template-lint no-pointer-down-event-binding](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-pointer-down-event-binding.md)\n- [MDN - Pointer events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events)\n- [MDN - mousedown event](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousedown_event)\n"
  },
  {
    "path": "docs/rules/template-no-positional-data-test-selectors.md",
    "content": "# ember/template-no-positional-data-test-selectors\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n## Motivation\n\n[ember-test-selectors](https://github.com/simplabs/ember-test-selectors) is a very popular library that enables better element selectors for testing.\n\nOne of the features that had been added to ember-test-selectors over the years was to allow passing a positional argument to curly component invocations as a shorthand (to avoid having to also add a named argument value).\n\nThat would look like:\n\n```hbs\n{{some-thing data-test-foo}}\n```\n\nInternally, that was converted to an `attributeBinding` for `@ember/component`s. Unfortunately, that particular invocation syntax is in conflict with modern Ember Octane templates. For example, in the snippet above `data-test-foo` is actually referring to `this.data-test-foo` (and would be marked as an error by the `no-implicit-this` rule).\n\nAdditionally, the nature of these \"fake\" local properties significantly confuses the codemods that are used to transition an application into Ember Octane (e.g. [ember-no-implicit-this-codemod](https://github.com/ember-codemods/ember-no-implicit-this-codemod) and [ember-angle-brackets-codemod](https://github.com/ember-codemods/ember-angle-brackets-codemod)).\n\n## Examples\n\nThis rule forbids the following:\n\n```hbs\n{{foo-bar data-test-blah}}\n{{#foo-bar data-test-blah}}{{/foo-bar}}\n```\n\nAnd suggests using the following instead:\n\n```hbs\n{{foo-bar data-test-blah=true}}\n{{#foo-bar data-test-blah=true}}{{/foo-bar}}\n```\n\n## References\n\n- [ember-test-selectors#d47f73d](https://github.com/simplabs/ember-test-selectors/commit/d47f73d76b3ccbc9f0be5df3b897afd08b1636a6)\n"
  },
  {
    "path": "docs/rules/template-no-positive-tabindex.md",
    "content": "# ember/template-no-positive-tabindex\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n## `<* tabindex>`\n\n[MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) explains the motivation of this rule nicely:\n\n> Avoid using tabindex values greater than 0. Doing so makes it difficult for people who rely on assistive technology to navigate and operate page content. Instead, write the document with the elements in a logical sequence.\n\nThis rule prevents usage of any `tabindex` values other than `0` and `-1`. It does allow for dynamic values (choosing which value to show based on some condition / helper / etc), but only if that inline `if` condition has static `0`/`-1` as the value.\n\nThis rule takes no arguments.\n\n## Examples\n\nThis rule **allows** the following:\n\n```hbs\n<span tabindex='0'>foo</span>\n<span tabindex='-1'>bar</span>\n<span tabindex={{0}}>baz</span>\n<button tabindex={{if this.isHidden '-1'}}>baz</button>\n<div role='tab' tabindex={{if this.isHidden '-1' '0'}}>baz</div>\n```\n\nThis rule **forbids** the following:\n\n```hbs\n<span tabindex='5'>foo</span>\n<span tabindex='3'>bar</span>\n<span tabindex={{dynamicValue}}>zoo</span>\n<span tabindex='1'>baz</span>\n<span tabindex='2'>never really sure what goes after baz</span>\n```\n\n## References\n\n1. [AX_FOCUS_03](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_03)\n1. [w3.org/TR/wai-aria-practices/#kbd_general_between](https://www.w3.org/TR/wai-aria-practices/#kbd_general_between)\n1. [w3.org/TR/2009/WD-wai-aria-practices-20090224/#focus_tabindex](https://www.w3.org/TR/2009/WD-wai-aria-practices-20090224/#focus_tabindex)\n1. [MDN: tabindex documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex)\n"
  },
  {
    "path": "docs/rules/template-no-potential-path-strings.md",
    "content": "# ember/template-no-potential-path-strings\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nIt might happen sometimes that `{{` and `}}` are forgotten when invoking a component, and the string that is passed was actually supposed to be a property path or argument.\n\nThis rule warns about all arguments and attributes that start with `this.` or `@`, but are missing the surrounding `{{` and `}}` characters.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<img src='this.picture' />\n```\n\n```hbs\n<img src='@img' />\n```\n\nThis rule **allows** the following:\n\n```hbs\n<img src={{this.picture}} />\n```\n\n```hbs\n<img src={{@img}} />\n```\n\n## Migration\n\n- Replace the surrounding `\"` characters with `{{`/`}}`\n\n## Related Rules\n\n- [no-arguments-for-html-elements](template-no-arguments-for-html-elements.md)\n\n## References\n\n- [Component Arguments and HTML Attributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/)\n"
  },
  {
    "path": "docs/rules/template-no-quoteless-attributes.md",
    "content": "# ember/template-no-quoteless-attributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nIn HTML, all attribute values are considered strings, regardless of whether they are quoted or not.\n\nThe following two examples are _identical_ from the perspective of the browser:\n\n<!-- prettier-ignore -->\n```html\n<div data-foo=asdf></div>\n<div data-foo=\"asdf\"></div>\n```\n\nThis fact makes the following HTML very confusing:\n\n<!-- prettier-ignore -->\n```html\n<input disabled=false>\n```\n\nIn this case, the simple _presence_ of the `disabled` attribute means that the `<input>` is disabled and setting the value to `false` doesn't do the obvious thing.\n\nThis is just _one_ (of many) cases where the default \"string\" based parsing of attributes in HTML can trip folks up.\n\nThis rule attempts to make this situation _slightly_ better by at least ensuring that all attribute values are quoted. This obviously doesn't fix the :troll:y nature of HTML here but it does ensure that you still **see** the quotes (which should hopefully help remind you that these are strings and not values).\n\n## Examples\n\nThis rule **forbids** the following (note that `someValue` could have been intended either as a string or expression):\n\n<!-- prettier-ignore -->\n```html\n<div data-foo=someValue></div>\n```\n\nThis rule **allows** the following:\n\n```html\n<div data-foo=\"someValue\"></div>\n```\n\n```hbs\n<div data-foo={{someValue}}></div>\n```\n\n## References\n\n- [HTML spec/attributes](https://html.spec.whatwg.org/multipage/dom.html#attributes)\n"
  },
  {
    "path": "docs/rules/template-no-redundant-fn.md",
    "content": "# ember/template-no-redundant-fn\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThe `fn` helper can be used to bind arguments to another function. Using it without any arguments is redundant because then the inner function could just be used directly.\n\nThis rule is looking for `fn` helper usages that don't provide any additional arguments to the inner function and warns about them.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<button {{on 'click' (fn this.handleClick)}}>Click Me</button>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<button {{on 'click' this.handleClick}}>Click Me</button>\n```\n\n```hbs\n<button {{on 'click' (fn this.handleClick 'foo')}}>Click Me</button>\n```\n\n## References\n\n- [Ember Guides](https://guides.emberjs.com/release/components/component-state-and-actions/#toc_passing-arguments-to-actions)\n- [`fn` API documentation](https://api.emberjs.com/ember/3.20/classes/Ember.Templates.helpers/methods/fn?anchor=fn)\n"
  },
  {
    "path": "docs/rules/template-no-redundant-role.md",
    "content": "# ember/template-no-redundant-role\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThe rule checks for redundancy between any semantic HTML element with a default/implicit ARIA role and the role provided.\n\nFor example, if a landmark element is used, any role provided will either be redundant or incorrect. This rule ensures that no role attribute is placed on any of the landmark elements, with the following exceptions:\n\n- a `nav` element with the `navigation` role to [make the structure of the page more accessible to user agents](https://www.w3.org/WAI/GL/wiki/Using_HTML5_nav_element#Example:The_.3Cnav.3E_element)\n- a `form` element with the `search` role to [identify the form's search functionality](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/search_role#examples)\n- a `input` element with `combobox` role to [identify the input as a combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-both/)\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<header role='banner'></header>\n```\n\n```hbs\n<main role='main'></main>\n```\n\n```hbs\n<aside role='complementary'></aside>\n```\n\n```hbs\n<footer role='contentinfo'></footer>\n```\n\n```hbs\n<form role='form'></form>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<form role='search'></form>\n```\n\n```hbs\n<nav role='navigation'></nav>\n```\n\n```hbs\n<input role='combobox' />\n```\n\n## Configuration\n\n- boolean -- if `true`, default configuration is applied\n\n- object -- containing the following property:\n  - boolean -- `checkAllHTMLElements` -- if `true`, the rule checks for redundancy between any semantic HTML element with a default/implicit ARIA role and the role provided, instead of just landmark roles (default: `true`)\n\n## References\n\n- [Landmark Roles (WAI-ARIA spec)](https://www.w3.org/WAI/PF/aria/roles#landmark_roles)\n- [Using ARIA landmarks to identify regions of a page](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA11)\n- [Document conformance requirements for use of ARIA attributes in HTML](https://www.w3.org/TR/html-aria/#docconformance)\n- [ARIA Spec, ARIA Adds Nothing to Default Semantics of Most HTML Elements](https://www.w3.org/TR/using-aria/#aria-does-nothing)\n- [Disabling a link](https://www.scottohara.me/blog/2021/05/28/disabled-links.html)\n"
  },
  {
    "path": "docs/rules/template-no-restricted-invocations.md",
    "content": "# ember/template-no-restricted-invocations\n\n<!-- end auto-generated rule header -->\n\nDisallow certain components, helpers or modifiers from being used.\n\nUse cases include:\n\n- You bring in some addon like ember-composable-helpers, but your team deems one or many of the helpers not suitable and wants to guard against their usage\n- You want to discourage use of a deprecated component\n\n## Examples\n\nGiven a config of:\n\n```json\n[\"foo-bar\"]\n```\n\nThis rule **forbids** the following:\n\n```hbs\n{{foo-bar}}\n```\n\n```hbs\n{{#foo-bar}}{{/foo-bar}}\n```\n\n```hbs\n<FooBar />\n```\n\n## Configuration\n\nOne of these:\n\n- string[] - helpers or components to disallow (using kebab-case names like `nested-scope/component-name`)\n- object[] - with the following keys:\n  - `names` - string[] - helpers or components to disallow (using kebab-case names like `nested-scope/component-name`)\n  - `message` - string - custom error message to report for violations (typically a deprecation notice / explanation of why not to use it and a recommended replacement)\n\n## Related Rules\n\n- [ember/no-restricted-service-injections](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-restricted-service-injections.md)\n\n## References\n\n- [ember-cli-deprecation-workflow](https://github.com/mixonic/ember-cli-deprecation-workflow)\n"
  },
  {
    "path": "docs/rules/template-no-route-action.md",
    "content": "# ember/template-no-route-action\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nThis rule disallows the usage of `route-action`.\n\n[ember-route-action-helper](https://github.com/DockYard/ember-route-action-helper) was a popular addon used to add actions to a route without creating a separate controller. Given the changes in Ember since ember-route-action-helper was a widely used pattern, controllers are now encouraged and we want to discourage the use of route-action.\n\nMost route actions should either be sent to the controller first or encapsulated within a downstream component instead. We should never be escaping the DDAU hierarchy to lob actions up to the route.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<CustomComponent @onUpdate={{route-action 'updateFoo'}} />\n```\n\n```hbs\n<CustomComponent @onUpdate={{route-action 'updateFoo' 'bar'}} />\n```\n\n```hbs\n{{custom-component onUpdate=(route-action 'updateFoo')}}\n```\n\n```hbs\n{{custom-component onUpdate=(route-action 'updateFoo' 'bar')}}\n```\n\nWith the given route:\n\n```js\n// app/routes/foo.js\nexport default class extends Route {\n  @action\n  updateFoo(baz) {\n    // ...\n  }\n}\n```\n\nThis rule **allows** the following:\n\n```hbs\n<CustomComponent @onUpdate={{this.updateFoo}} />\n```\n\n```hbs\n<CustomComponent @onUpdate={{fn this.updateFoo 'bar'}} />\n```\n\n```hbs\n{{custom-component onUpdate=this.updateFoo}}\n```\n\n```hbs\n{{custom-component onUpdate=(fn this.updateFoo 'bar')}}\n```\n\nWith the given controller:\n\n```js\n// app/controllers/foo.js\nexport default class extends Controller {\n  @action\n  updateFoo(baz) {\n    // ...\n  }\n}\n```\n\n## Migration\n\nThe example below shows how to migrate from route-action to controller actions.\n\n### Before\n\n```js\n// app/routes/posts.js\nexport default class extends Route {\n  model(params) {\n    return this.store.query('post', { page: params.page });\n  }\n\n  @action\n  goToPage(pageNum) {\n    this.transitionTo({ queryParams: { page: pageNum } });\n  }\n}\n```\n\n```js\n// app/controllers/posts.js\nexport default class extends Controller {\n  queryParams = ['page'];\n  page = 1;\n}\n```\n\n```hbs\n{{#each @model as |post|}}\n  <Post @title={{post.title}} @content={{post.content}} />\n{{/each}}\n\n<button {{action (route-action 'goToPage' 1)}}>1</button>\n<button {{action (route-action 'goToPage' 2)}}>2</button>\n<button {{action (route-action 'goToPage' 3)}}>3</button>\n```\n\n### After\n\n```js\n// app/routes/posts.js\nexport default class extends Route {\n  model(params) {\n    return this.store.query('post', { page: params.page });\n  }\n}\n```\n\n```js\n// app/controllers/posts.js\nexport default class extends Controller {\n  queryParams = ['page'];\n  page = 1;\n\n  @action\n  goToPage(pageNum) {\n    this.transitionToRoute({ queryParams: { page: pageNum } });\n  }\n}\n```\n\n```hbs\n{{#each @model as |post|}}\n  <Post @title={{post.title}} @content={{post.content}} />\n{{/each}}\n\n<button {{on 'click' (fn this.goToPage 1)}}>1</button>\n<button {{on 'click' (fn this.goToPage 2)}}>2</button>\n<button {{on 'click' (fn this.goToPage 3)}}>3</button>\n```\n\n## References\n\n- [ember-route-action-helper](https://github.com/DockYard/ember-route-action-helper)\n- [Ember guides/Controllers](https://guides.emberjs.com/release/routing/controllers/)\n- [Ember Best Practices: What are controllers good for?](https://dockyard.com/blog/2017/06/16/ember-best-practices-what-are-controllers-good-for)\n"
  },
  {
    "path": "docs/rules/template-no-scope-outside-table-headings.md",
    "content": "# ember/template-no-scope-outside-table-headings\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThe scope attribute is used on `<th>` elements to clarify the relationship between a table's header cells and data cells for screen readers. Scope is set to \"row\" or \"col\" for header cells that refer to a given row or column. For header cells that reference multiple rows or columns, set the scope attribute to \"rowgroup\" and \"colgroup\" and define the range for the rows or columns.\n\nThis rule disallows the use of the scope attribute on HTML elements other than the `<th>` element.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<a scope='col'></a>\n<table scope='rowgroup'></table>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<th scope='row'>A header cell</th>\n<CustomHeader scope={{foo}} />\n```\n\n## References\n\n- [HTML \\<th\\> scope Attribute](https://www.w3schools.com/tags/att_th_scope.asp)\n- [scope - eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/scope.md)\n"
  },
  {
    "path": "docs/rules/template-no-shadowed-elements.md",
    "content": "# ember/template-no-shadowed-elements\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule prevents ambiguity in situations where a yielded block param which starts with a lower case letter is also used within the block itself as an element name.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<FooBar as |div|>\n  <div></div>\n</FooBar>\n```\n\nThis rule **allows** the following:\n\n```hbs\n{{#foo-bar as |Baz|}}\n  <Baz />\n{{/foo-bar}}\n```\n\n```hbs\n<FooBar as |Baz|>\n  <Baz />\n</FooBar>\n```\n\n```hbs\n{{#with foo=(component 'blah-zorz') as |Div|}}\n  <Div />\n{{/with}}\n```\n\n```hbs\n<Foo as |bar|>\n  <bar.baz />\n</Foo>\n```\n\n## References\n\n- [Ember guides/block content](https://guides.emberjs.com/release/components/block-content/)\n- [rfcs/angle bracket invocation](https://emberjs.github.io/rfcs/0311-angle-bracket-invocation.html)\n- [rfcs/named blocks](https://emberjs.github.io/rfcs/0226-named-blocks.html)\n"
  },
  {
    "path": "docs/rules/template-no-splattributes-with-class.md",
    "content": "# ember/template-no-splattributes-with-class\n\n<!-- end auto-generated rule header -->\n\nThis rule enforces that when using `...attributes` (spread attributes), you should not also use a `class` attribute. The `...attributes` syntax is used to forward HTML attributes from a parent component to a child component, and it already handles class merging automatically.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<div ...attributes class='foo'>\n  content\n</div>\n```\n\n```hbs\n<div class='foo' ...attributes>\n  content\n</div>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<div ...attributes>\n  content\n</div>\n```\n\n```hbs\n<div class='foo'>\n  content\n</div>\n```\n\n## Why?\n\nWhen using `...attributes`, any classes passed from the parent component will be automatically merged with the component's own classes. Adding a `class` attribute alongside `...attributes` can lead to confusion about which classes take precedence and may result in unexpected styling behavior.\n\nFor example:\n\n```hbs\n{{! Parent component }}\n<MyComponent class='parent-class' />\n\n{{! MyComponent template }}\n<div ...attributes class='child-class'>\n  {{! This is confusing: which class takes precedence? }}\n</div>\n```\n\nInstead, you should either:\n\n1. Use `...attributes` alone to allow class merging from the parent\n2. Use `class` alone if you want to enforce specific classes\n\n## References\n\n- [Splattributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/#toc_html-attributes) in the Ember.js guides\n"
  },
  {
    "path": "docs/rules/template-no-this-in-template-only-components.md",
    "content": "# ember/template-no-this-in-template-only-components\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThere is no `this` context in template-only components.\n\n## Examples\n\nThis rule **forbids** `this` in template-only components:\n\n```hbs\n<h1>Hello {{this.name}}!</h1>\n```\n\nThe `--fix` option will convert to named arguments:\n\n```hbs\n<h1>Hello {{@name}}!</h1>\n```\n\n## Migration\n\n- use [ember-no-implicit-this-codemod](https://github.com/ember-codemods/ember-no-implicit-this-codemod)\n- [upgrade to Glimmer components](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/), which don't allow ambiguous access\n  - classic components have [auto-reflection](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md#motivation), and can use `this.myArgName` or `this.args.myArgNme` or `@myArgName` interchangeably\n\n## References\n\n- [Glimmer components](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/)\n- [rfcs/named args](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md#motivation)\n"
  },
  {
    "path": "docs/rules/template-no-trailing-spaces.md",
    "content": "# ember/template-no-trailing-spaces\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow trailing whitespace at the end of lines.\n\n## Examples\n\nIn examples below, `•` represents a trailing space character.\n\nThis rule **forbids** the following:\n\n```hbs\n<div>test</div>•• •••••\n```\n\n```gjs\n<template>\n  <div>Hello</div>••\n</template>\n```\n\nThis rule **allows** the following:\n\n```hbs\n<div>test</div>\n```\n\n```gjs\n<template>\n  <div>Hello</div>\n</template>\n```\n\n## Related Rules\n\n- [no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces) from eslint\n\n## References\n\n- [git/formatting and whitespace](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration#_formatting_and_whitespace)\n"
  },
  {
    "path": "docs/rules/template-no-triple-curlies.md",
    "content": "# ember/template-no-triple-curlies\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUsage of triple curly braces to allow raw HTML to be injected into the DOM is a large vector for exploits of your application (especially when the raw HTML is user-controllable). Instead of using `{{{foo}}}`, you should use appropriate helpers or computed properties that return a `SafeString` (via `Ember.String.htmlSafe` generally) and ensure that user-supplied data is properly escaped.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{{foo}}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{foo}}\n</template>\n```\n\n## References\n\n- See the [documentation](https://api.emberjs.com/ember/release/functions/@ember%2Ftemplate/htmlSafe) for Ember's `htmlSafe` function\n"
  },
  {
    "path": "docs/rules/template-no-unavailable-this.md",
    "content": "# ember/template-no-unavailable-this\n\n<!-- end auto-generated rule header -->\n\nDisallow `this` in templates that are not inside a class or function.\n\n## Rule Details\n\nIn Ember and Glimmer, `this` refers to the component instance. When a `<template>` tag is used at module level (not inside a class body or function), `this` has no meaningful value and will be `undefined`. This rule catches accidental usage of `this` in such templates.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{this.name}}\n</template>\n```\n\n```gjs\n<template>\n  {{yield this}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\nimport Component from '@glimmer/component';\n\nclass MyComponent extends Component {\n  <template>{{this.name}}</template>\n}\n```\n\n```gjs\nfunction myComponent() {\n  return <template>{{this.name}}</template>;\n}\n```\n\n```gjs\n<template>\n  {{@value}}\n</template>\n```\n\n## References\n\n- [Glimmer Components](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/)\n"
  },
  {
    "path": "docs/rules/template-no-unbalanced-curlies.md",
    "content": "# ember/template-no-unbalanced-curlies\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nNormally, the compiler will find stray curlies and throw a syntax error. However, it won't be able to catch every case.\n\nFor example, these are all syntax errors:\n\n```gjs\n<template>\n  {{ x }\n  {{ x }}}\n  {{{ x }\n  {{{ x }}\n</template>\n```\n\nWhereas these are not:\n\n```gjs\n<template>\n  { x }}\n  { x }\n  }\n  }}\n  }}}\n  }}}}... (any number of closing curlies past one)\n</template>\n```\n\nThis rule focuses on closing double `}}` and triple `}}}` curlies with no matching opening curlies.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  foo}}\n  {foo}}\n  foo}}}\n  {foo}}}\n</template>\n```\n\n## Migration\n\nIf you have curlies in your code that you wish to show verbatim, but are flagged by this rule, you can formulate them as a handlebars expression:\n\n```gjs\n<template>\n  <p>This is a closing double curly: {{ '}}' }}</p>\n  <p>This is a closing triple curly: {{ '}}}' }}</p>\n</template>\n```\n\n## References\n\n- [Handlebars docs/expressions](https://handlebarsjs.com/guide/expressions.html)\n"
  },
  {
    "path": "docs/rules/template-no-unbound.md",
    "content": "# ember/template-no-unbound\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n`{{unbound}}` is a legacy hold over from the days in which Ember's template engine was less performant. Its use today\nis vestigial, and it no longer offers performance benefits.\n\nIt is also a poor practice to use it for rendering only the initial value of a property that may later change.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{unbound aVar}}\n</template>\n```\n\n```gjs\n<template>\n  {{some-component foo=(unbound aVar)}}\n</template>\n```\n\n## References\n\n- [deprecations/unbound block syntax](https://deprecations.emberjs.com/v1.x/#toc_block-and-multi-argument-unbound-helper)\n- [Ember api/unbound helper](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/each?anchor=unbound)\n"
  },
  {
    "path": "docs/rules/template-no-unknown-arguments-for-builtin-components.md",
    "content": "# ember/template-no-unknown-arguments-for-builtin-components\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThe builtin components `LinkTo`, `Input`, `Textarea` has list of allowed arguments, and some argument names may be mistyped, this rule trying to highlight possible typos, checking for unknown arguments, also, some components has conflicted and required arguments, rule addressing this behavior.\n\nThis rule warns about `unknown`, `required` and `conflicted` arguments for `LinkTo`, `Input`, `Textarea` components.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n<LinkTo @unsupportedArgument='foo'> some link with unknown argument</LinkTo>\n<LinkTo @route='info' @model='a' @models='b'> info </LinkTo>\n<LinkTo @models='b'> info </LinkTo>\n```\n\n```hbs\n<Input @foo='bar' />\n```\n\n```hbs\n<Textarea @foo='bar' />\n```\n\nThis rule **allows** the following:\n\n```hbs\n<LinkTo @route='readme'> readme </LinkTo>\n```\n\n```hbs\n<Input @value='someValue' />\n```\n\n```hbs\n<Textarea @value='someValue' />\n```\n\n## Migration\n\n- Check references section to get allowed arguments list.\n- If argument represents html attribute, remove `@` from name.\n\n## Related Rules\n\n- [no-link-to-tagname](template-no-link-to-tagname.md)\n- [no-input-tagname](template-no-input-tagname.md)\n- [builtin-component-arguments](template-builtin-component-arguments.md)\n\n## References\n\n- [Reduce API Surface of Built-In Components](https://github.com/emberjs/rfcs/blob/master/text/0707-modernize-built-in-components-2.md#summary)\n"
  },
  {
    "path": "docs/rules/template-no-unnecessary-component-helper.md",
    "content": "# ember/template-no-unnecessary-component-helper\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nThe `component` template helper can be used to dynamically pick the component being rendered based on the provided property. But if the component name is passed as a string because it's already known, then the component should be invoked directly, instead of using the `component` helper.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{component \"my-component\"}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{component SOME_COMPONENT_NAME}}\n</template>\n```\n\n```gjs\n<template>\n  {{!-- the `component` helper is needed to invoke this --}}\n  {{component \"addon-name@component-name\"}}\n</template>\n```\n\n```gjs\n<template>\n  {{my-component}}\n</template>\n```\n\n```gjs\n<template>\n  {{my-component close=(component \"link-to\" \"index\")}}\n  <MyComponent @close={{component \"link-to\" \"index\"}} />\n</template>\n```\n\n## References\n\n- [component helper guide](https://guides.emberjs.com/release/components/defining-a-component/#toc_dynamically-rendering-a-component)\n- [component helper spec](https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/component?anchor=component)\n"
  },
  {
    "path": "docs/rules/template-no-unnecessary-concat.md",
    "content": "# ember/template-no-unnecessary-concat\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n💅 The `extends: 'stylistic'` property in a configuration file enables this rule.\n\nThis rule forbids unnecessary use of quotes (`\"\"`) around expressions like `{{myValue}}`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <span class=\"{{if errors.length 'text-danger' 'text-grey'}}\">\n  <img src=\"{{customSrc}}\" alt=\"{{customAlt}}\">\n  <label for=\"{{concat elementId \"-date\"}}\">\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <span class={{if errors.length 'text-danger' 'text-grey'}}>\n  <img src={{customSrc}} alt={{customAlt}}>\n  <label for={{concat elementId \"-date\"}}>\n</template>\n```\n\n## Migration\n\nUse regexp find-and-replace to fix existing violations of this rule:\n\n| Before           | After     |\n| ---------------- | --------- |\n| `=\"{{([^}]+)}}\"` | `={{$1}}` |\n\n## References\n\n- [Handlebars docs/expressions](https://handlebarsjs.com/guide/expressions.html)\n- [Ember api/concat helper](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/concat?anchor=concat)\n"
  },
  {
    "path": "docs/rules/template-no-unnecessary-curly-parens.md",
    "content": "# ember/template-no-unnecessary-curly-parens\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow unnecessary parentheses enclosing statements in curlies. When invoking a helper with arguments, the outer parentheses around the entire expression are unnecessary.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{(concat \"a\" \"b\")}}\n</template>\n```\n\n```gjs\n<template>\n  {{(helper a=\"b\")}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{foo}}\n  {{(foo)}}\n  {{concat \"a\" \"b\"}}\n  {{concat (capitalize \"foo\") \"-bar\"}}\n</template>\n```\n\n## References\n\n- Ember's [Helper Functions](https://guides.emberjs.com/release/components/helper-functions/) guide\n"
  },
  {
    "path": "docs/rules/template-no-unnecessary-curly-strings.md",
    "content": "# ember/template-no-unnecessary-curly-strings\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nStrings need not be wrapped in curly braces (mustache expressions).\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><FooBar class={{\"btn\"}} /></template>\n```\n\n```gjs\n<template><FooBar class=\"btn\">{{\"Hello\"}}</FooBar></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><FooBar class=\"btn\" /></template>\n```\n\n```gjs\n<template><FooBar class=\"btn\">Hello</FooBar></template>\n```\n\n## References\n\n- [Handlebars expressions](https://handlebarsjs.com/guide/expressions.html)\n"
  },
  {
    "path": "docs/rules/template-no-unsupported-role-attributes.md",
    "content": "# ember/template-no-unsupported-role-attributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nMany ARIA states and properties are only available to elements with particular roles. This ensures that the appropriate information gets exposed to a browser's accessibility API for the given element.\n\nThis rule disallows the use of ARIA properties unsupported by an element's defined role. An element's role may either be explicitly set by the `role` attribute, or it may be implicitly defined through the use of HTML elements with inherent roles. For example, `<input type=\"checkbox\"` has the implicit role of `checkbox`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div role=\"link\" href=\"#\" aria-checked />\n  <input type=\"checkbox\" aria-invalid=\"grammar\" />\n  <CustomComponent role=\"listbox\" aria-level=\"2\" />\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <div role=\"heading\" aria-level=\"1\" />\n  <input type=\"image\" aria-atomic />\n  <CustomComponent role=\"textbox\" aria-required=\"true\" />\n</template>\n```\n\n## References\n\n- [Using ARIA, Roles, States, and Properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques)\n"
  },
  {
    "path": "docs/rules/template-no-unused-block-params.md",
    "content": "# ember/template-no-unused-block-params\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis rule forbids unused block parameters except when they are needed to access a later parameter.\n\n## Examples\n\nThis rule **forbids** the following (unused parameters):\n\n```gjs\n<template>\n  {{#each users as |user index|}}\n    {{user.name}}\n  {{/each}}\n</template>\n```\n\nThis rule **allows** the following:\n\nAllowed (used parameters):\n\n```gjs\n<template>\n  {{#each users as |user|}}\n    {{user.name}}\n  {{/each}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each users as |user index|}}\n    {{index}} {{user.name}}\n  {{/each}}\n</template>\n```\n\nAllowed (later parameter used):\n\n```gjs\n<template>\n  {{#each users as |user index|}}\n    {{index}}\n  {{/each}}\n</template>\n```\n\n## Related rules\n\n- [eslint/no-unused-vars](https://eslint.org/docs/rules/no-unused-vars)\n\n## References\n\n- [Ember guides/block content](https://guides.emberjs.com/release/components/block-content/)\n- [rfcs/angle bracket invocation](https://emberjs.github.io/rfcs/0311-angle-bracket-invocation.html)\n- [rfcs/named blocks](https://emberjs.github.io/rfcs/0226-named-blocks.html)\n"
  },
  {
    "path": "docs/rules/template-no-valueless-arguments.md",
    "content": "# ember/template-no-valueless-arguments\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nSimilar to HTML attributes, component arguments will default to an empty string when they are not explicitly assigned a value. This behavior isn't documented anywhere so depending on it isn't recommended and usually the result of a user-error. Since it _is_ valid syntax, accidental use of this behavior can be hard to detect and cause confusing bugs.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><Editor @defaultText /></template>\n```\n\n```gjs\n<template><SomeComponent @valuelessByAccident{{@canBeAModifier}} /></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><Editor @defaultText=\"\" /></template>\n```\n\n## Migration\n\nExplicitly assign a value to all arguments.\n\n## Related Rules\n\n- [ember/no-quoteless-attributes](no-quoteless-attributes.md)\n\n## References\n\n- [RFC-311: angle bracket invocation](https://emberjs.github.io/rfcs/0311-angle-bracket-invocation.html)\n- [Attributes in the HTML specification](https://html.spec.whatwg.org/#syntax-attributes))\n"
  },
  {
    "path": "docs/rules/template-no-whitespace-for-layout.md",
    "content": "# ember/template-no-whitespace-for-layout\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nFormatting of text through the use of multiple whitespace is entirely visual, and therefore is incompatible with screen-reading assistive technology tools.\n\nThe rule applies to the content of Handlebars AST TextNodes, and performs a RegExp search for two consecutive white space characters that might indicate the use of whitespace used for layout.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  Mon.&nbsp;&nbsp;&nbsp;&nbsp;Eggs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Tomato soup&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;House salad<br>\n  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bacon&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Hamburger&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fried chicken<br>\n  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Toast&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Onion rings&nbsp;&nbsp;&nbsp;&nbsp;Green beans<br>\n  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Cookie&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Mashed potatoes\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <p>Start to finish</p>\n</template>\n```\n\n```gjs\n<template>\n  <p>Start&nbsp;to&nbsp;Finish</p>\n</template>\n```\n\n## Migration\n\nTo fix issues caused by using whitespace for layout, the following are recommended:\n\n- use the appropriate HTML markup to contain the information\n- use CSS to add padding or margins to the semantic HTML markup\n\n## References\n\n- [F33: Using white space characters to create multiple columns in plain text content](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F33)\n- [F34: Using white space characters to format tables in plain text content](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F34)\n"
  },
  {
    "path": "docs/rules/template-no-whitespace-within-word.md",
    "content": "# ember/template-no-whitespace-within-word\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nIn practice, the predominant issue raised by inline whitespace styling is that the resultant text \"formatting\" is entirely visual in nature; the ability to discern the correct manner in which to read the text, and therefore, to correctly comprehend its meaning, is restricted to sighted users.\n\nUsing in-line whitespace word formatting produces results that are explicitly mentioned in [WCAG's list of common sources of web accessibility failures](https://www.w3.org/TR/WCAG20-TECHS/failures.html). Specifically, this common whitespace-within-word-induced web accessibility issue fails to successfully achieve [WCAG Success Criterion 1.3.2: Meaningful Sequence](https://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-sequence.html).\n\nThe `template-no-whitespace-within-word` rule operates on the assumption that artificially-spaced English words in rendered text content contain, at a minimum, two word characters fencepost-delimited by three whitespace characters (`space-char-space-char-space`) so it should be avoided.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  W e l c o m e\n</template>\n```\n\n`W`**`&nbsp;`**`e`**`&nbsp;`**`l`**`&nbsp;`**`c`**`&nbsp;`**`o`**`&nbsp;`**`m`**`&nbsp;`**`e`\n\n`Wel c o me`\n\n`Wel`**`&nbsp;`**`c`**`&emsp;`**`o`**`&nbsp;`**`me`\n\n```gjs\n<template>\n  <div>W e l c o m e</div>\n\n  <div>Wel c o me</div>\n</template>\n```\n\nThis rule **allows** the following:\n\n`Welcome`\n\n`Yes`**`&nbsp;`**`I`**`&nbsp;`**`am`\n\n`It is possible to get some examples of in-word emph a sis past this rule.`\n\n`However, I do not want a rule that flags annoying false positives for correctly-used single-character words.`\n\n```gjs\n<template>\n  <div>Welcome</div>\n\n  <div>Yes&nbsp;I am.</div>\n</template>\n```\n\nThis rule uses the heuristic of letter, whitespace character, letter, whitespace character, letter which makes it a good candidate for most use cases, but not ideal for some languages (such as Japanese).\n\n## Migration\n\nUse CSS to add letter-spacing to a word.\n\n## References\n\n- [F32: Using white space characters to create multiple columns in plain text content](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F32)\n- [WCAG Success Criterion 1.3.2: Meaningful Sequence](https://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-sequence.html)\n- [C8: Using CSS letter-spacing to control spacing within a word](https://www.w3.org/WAI/WCAG21/Techniques/css/C8)\n"
  },
  {
    "path": "docs/rules/template-no-with.md",
    "content": "# ember/template-no-with\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nThe use of `{{with}}` has been deprecated, you should replace it with either `{{let}}` or a combination of `{{let}}`, `{{if}}` and `{{else}}`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{#with (hash name=\"Ben\" age=4) as |person|}}\n    Hi {{person.name}}, you are {{person.age}} years old.\n  {{/with}}\n</template>\n```\n\n```gjs\n<template>\n  {{#with user.posts as |blogPosts|}}\n    There are {{blogPosts.length}} blog posts.\n  {{/with}}\n</template>\n```\n\n```gjs\n<template>\n  {{#with user.posts as |blogPosts|}}\n    There are {{blogPosts.length}} blog posts.\n  {{else}}\n    There are no blog posts.\n  {{/with}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{#let (hash name=\"Ben\" age=4) as |person|}}\n    Hi {{person.name}}, you are {{person.age}} years old.\n  {{/let}}\n</template>\n```\n\n```gjs\n<template>\n  {{#let user.posts as |blogPosts|}}\n    {{#if blogPosts.length}}\n      There are {{blogPosts.length}} blog posts.\n    {{/if}}\n  {{/let}}\n</template>\n```\n\n```gjs\n<template>\n  {{#let user.posts as |blogPosts|}}\n    {{#if blogPosts.length}}\n      There are {{blogPosts.length}} blog posts.\n    {{else}}\n      There are no blog posts.\n    {{/if}}\n  {{/let}}\n</template>\n```\n\n## References\n\n- [Deprecate {{with}} RFC](https://github.com/emberjs/rfcs/blob/master/text/0445-deprecate-with.md)\n- More information is available at the [Deprecation Guide](https://deprecations.emberjs.com/v3.x/#toc_ember-glimmer-with-syntax)\n"
  },
  {
    "path": "docs/rules/template-no-yield-block-params-to-else-inverse.md",
    "content": "# ember/template-no-yield-block-params-to-else-inverse\n\n<!-- end auto-generated rule header -->\n\nYielding to else block is mainly useful for supporting curly invocation syntax. However, the else block in curly invocation syntax does not support consuming block params.\n\nYielding block params (positional arguments) to `else` or `inverse` blocks doesn't work as expected. The params are not available in the inverse block.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{yield 'some' 'param' to='else'}}\n</template>\n```\n\n```gjs\n<template>\n  {{yield 'some' 'param' to='inverse'}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{yield}}\n  {{yield to='else'}}\n  {{yield to='inverse'}}\n</template>\n```\n\n## Migration\n\nWe need to remove block params from highlighted yield's and update application logic to not consume it.\n\nIn addition, we could use named blocks (slots) to provide values.\n\n## References\n\n- [Ember Guides – Block content](https://guides.emberjs.com/v5.5.0/components/block-content/)\n\n## Related Rules\n\n- [no-yield-only](template-no-yield-only.md)\n- [no-yield-to-default](template-no-yield-to-default.md)\n"
  },
  {
    "path": "docs/rules/template-no-yield-only.md",
    "content": "# ember/template-no-yield-only\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nTemplates that only contain a single `{{yield}}` instruction are not required\nand increase the total template payload size.\n\nThis rule warns about templates that only contain a single `{{yield}}`\ninstruction.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{yield}}\n</template>\n```\n\n```gjs\n<template>\n\n   {{yield}}\n\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{yield something}}\n</template>\n```\n\n```gjs\n<template>\n  <div>{{yield}}</div>\n</template>\n```\n\n## Migration\n\n- delete all files that are flagged by this rule\n\n## References\n\n- <https://github.com/ember-template-lint/ember-template-lint/issues/29>\n"
  },
  {
    "path": "docs/rules/template-no-yield-to-default.md",
    "content": "# ember/template-no-yield-to-default\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThe `yield` keyword can be used for invoking blocks passed into a component. The `to` named argument specifies which of the blocks to yield too. Specifying `{{yield to=\"default\"}}` is unnecessary, as that is the default behavior. Likewise, `{{has-block}}` and `{{has-block-params}}` also defaults to checking the \"default\" block.\n\nThis rule disallow yield to named blocks with the name \"default\".\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{yield to=\"default\"}}\n</template>\n```\n\n```gjs\n<template>\n  {{has-block \"default\"}}\n</template>\n```\n\n```gjs\n<template>\n  {{has-block-params \"default\"}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{yield}}\n</template>\n```\n\n```gjs\n<template>\n  {{has-block}}\n</template>\n```\n\n```gjs\n<template>\n  {{has-block-params}}\n</template>\n```\n\n## References\n"
  },
  {
    "path": "docs/rules/template-quotes.md",
    "content": "# ember/template-quotes\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nEnforce the consistent use of either double or single quotes.\n\n## Examples\n\nEnforce either:\n\n```gjs\n<template>\n  <div class='my-class'>test</div>\n  {{my-helper 'hello there'}}\n</template>\n```\n\nor:\n\n```gjs\n<template>\n  <div class=\"my-class\">test</div>\n  {{my-helper \"hello there\"}}\n</template>\n```\n\n## Configuration\n\nThe following values are valid configuration:\n\n- string -- `\"double\"` requires the use of double quotes wherever possible, `\"single\"` requires the use of single quotes wherever possible\n- object -- `{ curlies: \"single\"|\"double\"|false, html: \"single\"|\"double\"|false }` - requires different quotes for Handlebars and HTML syntax\n\nFor the object config, the properties `curlies` and `html` can be passed one of the following values: `\"single\"`, `\"double\"`, or `false`. If `false` is passed to a property, it will be as if this rule is turned off for that specific syntax.\n\nWith the config `{ curlies: false, html: \"double\" }`, this would be **forbidden**:\n\n```gjs\n<template>\n  <div foo='bar'></div>\n</template>\n```\n\nHowever, this would be **allowed**:\n\n```gjs\n<template>\n  {{component \"foo\"}}\n  {{test x='y'}}\n  <div foo=\"bar\"></div>\n</template>\n```\n\n## Related Rules\n\n- [quotes](https://eslint.org/docs/rules/quotes) from eslint\n\n## References\n\n- [Google style guide/quotes](https://google.github.io/styleguide/htmlcssguide.html#HTML_Quotation_Marks)\n"
  },
  {
    "path": "docs/rules/template-require-aria-activedescendant-tabindex.md",
    "content": "# ember/template-require-aria-activedescendant-tabindex\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule requires non-interactive HTML elements using the `aria-activedescendant` attribute to declare a `tabindex` of `0` or `-1`.\n\nThe `aria-activedescendant` attribute identifies the active descendant element of a composite widget, textbox, group, or application with document focus. This attribute is placed on the container element of the input control, and its value is set to the ID of the active child element. This allows screen readers to communicate information about the currently active element as if it has focus, while actual focus of the DOM remains on the container element.\n\nElements with `aria-activedescendant` must be focusable to support keyboard navigation. `tabindex=\"0\"` puts the element in the natural tab order; `tabindex=\"-1\"` makes it focusable programmatically (e.g. via roving focus) but skips it in the tab order. Both are valid patterns for composite widgets — see the [W3C APG — Managing focus in composites using aria-activedescendant](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant).\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div aria-activedescendant='some-id'></div>\n  <div aria-activedescendant='some-id' tabindex='-2'></div>\n  <input aria-activedescendant={{some-id}} tabindex='-100' />\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <CustomComponent />\n  <CustomComponent aria-activedescendant={{some-id}} />\n  <CustomComponent aria-activedescendant={{some-id}} tabindex={{0}} />\n  <div aria-activedescendant='some-id' tabindex='0'></div>\n  <div aria-activedescendant='some-id' tabindex='-1'></div>\n  <input />\n  <input aria-activedescendant={{some-id}} />\n  <input aria-activedescendant={{some-id}} tabindex={{0}} />\n  <input aria-activedescendant={{some-id}} tabindex={{-1}} />\n</template>\n```\n\n## References\n\n- [MDN, Using the aria-activedescendant attribute(property)](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-activedescendant_attribute)\n- [WAI-aria: aria-activedescendant(property](https://www.digitala11y.com/aria-activedescendant-properties/)\n- [aria-activedescendant-has-tabindex - eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-activedescendant-has-tabindex.md)\n"
  },
  {
    "path": "docs/rules/template-require-button-type.md",
    "content": "# ember/template-require-button-type\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule requires all `<button>` elements to have a valid `type` attribute.\n\nBy default, the `type` attribute of `<button>` elements is `submit`. This can\nbe very confusing, when a button component is developed in isolation without\n`type=\"button\"`, and when inside a `<form>` element it suddenly starts to\nsubmit the form.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <button>Hello World!</button>\n  <button type=''>Hello World!</button>\n  <button type='invalid'>Hello World!</button>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <button type='button'>Hello World!</button>\n  <button type='submit'>Hello World!</button>\n  <button type='reset'>Hello World!</button>\n</template>\n```\n\n## References\n\n- [HTML spec - the button element](https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type)\n"
  },
  {
    "path": "docs/rules/template-require-context-role.md",
    "content": "# ember/template-require-context-role\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n## `<* role><* role /></*>`\n\nThe required context role defines the owning container where this role is allowed. If a role has a required context, authors MUST ensure that an element with the role is contained inside (or owned by) an element with the required context role. For example, an element with `role=\"listitem\"` is only meaningful when contained inside (or owned by) an element with `role=\"list\"`. You may place intermediate elements with `role=\"presentation\"` or `role=\"none\"` to remove their semantic meaning.\n\n## Roles to check\n\nFormat: role | required context role\n\n- columnheader | row\n- gridcell | row\n- listitem | group or list\n- menuitem | group, menu, or menubar\n- menuitemcheckbox | menu or menubar\n- menuitemradio | group, menu, or menubar\n- option | listbox\n- row | grid, rowgroup, or treegrid\n- rowgroup | grid\n- rowheader | row\n- tab | tablist\n- treeitem | group or tree\n\n## Examples\n\nThis rule **allows** the following:\n\n```hbs\n<div role='list'>\n  <div role='listitem'>Item One</div>\n  <div role='listitem'>Item Two</div>\n</div>\n```\n\n```hbs\n<div role='menu'>\n  <div role='presentation'>\n    <a role='menuitem'>Item One</a>\n  </div>\n  <div role='presentation'>\n    <a role='menuitem'>Item Two</a>\n  </div>\n</div>\n```\n\nThis rule **forbids** the following:\n\n```hbs\n<div>\n  <div role='listitem'>Item One</div>\n  <div role='listitem'>Item Two</div>\n</div>\n```\n\n```hbs\n<div role='menu'>\n  <div role='button'>\n    <a role='menuitem'>Item One</a>\n  </div>\n  <div>\n    <a role='menuitem'>Item Two</a>\n  </div>\n</div>\n```\n\n### References\n\n1. <https://www.w3.org/TR/wai-aria-1.1/#scope>\n1. <https://www.w3.org/TR/wai-aria-1.1/#columnheader>\n1. <https://www.w3.org/TR/wai-aria-1.1/#gridcell>\n1. <https://www.w3.org/TR/wai-aria-1.1/#listitem>\n1. <https://www.w3.org/TR/wai-aria-1.1/#menuitem>\n1. <https://www.w3.org/TR/wai-aria-1.1/#menuitemcheckbox>\n1. <https://www.w3.org/TR/wai-aria-1.1/#menuitemradio>\n1. <https://www.w3.org/TR/wai-aria-1.1/#option>\n1. <https://www.w3.org/TR/wai-aria-1.1/#row>\n1. <https://www.w3.org/TR/wai-aria-1.1/#rowgroup>\n1. <https://www.w3.org/TR/wai-aria-1.1/#rowheader>\n1. <https://www.w3.org/TR/wai-aria-1.1/#tab>\n1. <https://www.w3.org/TR/wai-aria-1.1/#treeitem>\n"
  },
  {
    "path": "docs/rules/template-require-each-key.md",
    "content": "# ember/template-require-each-key\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nIn order to improve rendering speed, Ember will try to reuse the DOM elements where possible. Specifically, if the same item is present in the array both before and after the change, its DOM output will be reused.\n\nThe key option is used to tell Ember how to determine if the items in the array being iterated over with `{{#each}}` has changed between renders. By default the item's object identity is used.\n\nThis is usually sufficient, so in most cases, the key option is simply not needed. However, in some rare cases, the objects' identities may change even though they represent the same underlying data.\n\nFor example:\n\n```js\npeople.map((person) => {\n  return { ...person, type: 'developer' };\n});\n```\n\nIn this case, each time the people array is map-ed over, it will produce an new array with completely different objects between renders. In these cases, you can help Ember determine how these objects related to each other with the key option:\n\n```gjs\n<template>\n  <ul>\n    {{#each @developers key='name' as |person|}}\n      <li>Hello, {{person.name}}!</li>\n    {{/each}}\n  </ul>\n</template>\n```\n\nBy doing so, Ember will use the value of the property specified (`person.name` in the example) to find a \"match\" from the previous render. That is, if Ember has previously seen an object from the `@developers` array with a matching name, its DOM elements will be re-used.\n\nThis rule will require to always use `key` with `{{#each}}`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{#each this.items as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items key='@invalid' as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items key='' as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{#each this.items key='id' as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items key='deeply.nested.id' as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items key='@index' as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\n```gjs\n<template>\n  {{#each this.items key='@identity' as |item|}}\n    <div>{{item.name}}</div>\n  {{/each}}\n</template>\n```\n\n## References\n\n- [Specifying Keys](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/each#specifying-keys)\n- [The Immutable Pattern in Tracked Properties](https://guides.emberjs.com/release/in-depth-topics/autotracking-in-depth/)\n"
  },
  {
    "path": "docs/rules/template-require-form-method.md",
    "content": "# ember/template-require-form-method\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule requires all `<form>` elements to have `method` attribute with `POST`, `GET` or `DIALOG` value.\n\nBy default `form` elements without `method` attribute are submitted as `GET` requests.\nIn usual applications `submit` event listeners are attached to `form` elements and `event.preventDefault()` is called to avoid form submission.\n\nHowever in case of failure to prevent default action, form submission as `GET` request can leak sensitive end-user information.\n\nExample uses of `GET` requests:\n\n- non-secure data\n- bookmarking the submission result\n- data search query strings\n\n**Caution** - this rules does not check for `formmethod` attribute on `form` elements themselves.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <form>Hello world!</form>\n  <form method=''></form>\n  <form method='random'>Hello world!</form>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <form method='post'>Hello world!</form>\n  <form method='get'>Hello world!</form>\n  <form method='dialog'>Hello world!</form>\n</template>\n```\n\n## Configuration\n\nThe following values are valid configuration:\n\n- boolean - `true` to enable / `false` to disable\n- object -- An object with the following keys:\n  - `allowedMethods` -- An array of allowed form `method` attribute values, default: `['POST', 'GET', 'DIALOG']`\n\n## References\n\n- [MDN - form method attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method)\n- [HTML spec - form method attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method)\n"
  },
  {
    "path": "docs/rules/template-require-has-block-helper.md",
    "content": "# ember/template-require-has-block-helper\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nIn Ember 3.26 the properties `hasBlock` and `hasBlockParams` were deprecated. Their replacement is to use `has-block` and `has-block-params` helpers instead.\n\nThis rule prevents the usage of `hasBlock` and `hasBlockParams` and suggests using `has-block` or `has-block-params` instead.\n\nFor more information about this deprecation you can view the [RFC](https://github.com/emberjs/rfcs/blob/master/text/0689-deprecate-has-block.md) or its entry on the [Deprecations page](https://deprecations.emberjs.com/v3.x/#toc_has-block-and-has-block-params).\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  {{hasBlock}}\n  {{#if hasBlock}}\n\n  {{/if}}\n</template>\n```\n\n```gjs\n<template>\n  {{hasBlockParams}}\n  {{#if hasBlockParams}}\n\n  {{/if}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  {{has-block}}\n  {{#if (has-block)}}\n\n  {{/if}}\n</template>\n```\n\n```gjs\n<template>\n  {{has-block-params}}\n  {{#if (has-block-params)}}\n\n  {{/if}}\n</template>\n```\n\n## Migration\n\n- `{{hasBlock}}` -> `{{has-block}}`\n- `{{hasBlockParams}}` -> `{{has-block-params}}`\n- `{{#if hasBlock}} {{/if}}` -> `{{#if (has-block)}} {{/if}}`\n- `{{#if (hasBlock \"inverse\")}} {{/if}}` -> `{{#if (has-block \"inverse\")}} {{/if}}`\n- `{{#if hasBlockParams}} {{/if}}` -> `{{#if (has-block-params)}} {{/if}}`\n- `{{#if (hasBlockParams \"inverse\")}} {{/if}}` -> `{{#if (has-block-params \"inverse\")}} {{/if}}`\n\n## References\n\n- [RFC](https://github.com/emberjs/rfcs/blob/master/text/0689-deprecate-has-block.md)\n- [Deprecation information](https://deprecations.emberjs.com/v3.x/#toc_has-block-and-has-block-params)\n"
  },
  {
    "path": "docs/rules/template-require-iframe-src-attribute.md",
    "content": "# ember/template-require-iframe-src-attribute\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nOmitting the `src` attribute from an `<iframe>` element can silently bypass your Content Security Policy's `frame-src` directive.\n\nWhen an `<iframe>` has no `src` (or an empty `src`), it implicitly loads `about:blank`. This document inherits the origin of the parent page, allowing the iframe to operate under the same-origin policy. Later dynamically setting `src` (e.g., via JavaScript) does not re-validate against `frame-src`, which exposes an **elevation-of-privilege vector**.\n\nThis rule ensures that all `<iframe>` elements specify a `src` attribute explicitly in the markup, even if it is a placeholder like `\"about:blank\"` or a safe data URL.\n\n## 🚨 Why this matters\n\nAn attacker could inject a seemingly harmless `<iframe>` into your template, then programmatically change its `src`. Without a defined `src` at load time, the browser grants it origin privileges that persist **after the `src` is changed**, effectively sidestepping CSP.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <iframe></iframe>\n</template>\n```\n\n```gjs\n<template>\n  <iframe {{this.setFrameElement}}></iframe>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <iframe src='about:blank'></iframe>\n</template>\n```\n\n```gjs\n<template>\n  <iframe src='/safe-path' {{this.setFrameElement}}></iframe>\n</template>\n```\n\n```gjs\n<template>\n  <iframe src='data:text/html,<h1>safe</h1>'></iframe>\n</template>\n```\n\n```gjs\n<template>\n  <iframe src=''></iframe>\n</template>\n```\n\n## Migration\n\nIf you're dynamically setting the `src`, pre-populate the element with a secure initial `src` to ensure CSP applies:\n\n```gjs\n<template>\n  <iframe src='about:blank' {{this.setFrameElement}}></iframe>\n</template>\n```\n\nOr, if you know the eventual value ahead of time:\n\n```gjs\n<template>\n  <iframe src='/iframe-entry' {{this.setFrameElement}}></iframe>\n</template>\n```\n\n## Related Rules\n\n- [require-iframe-title](template-require-iframe-title.md)\n\n## References\n\n- [CSP `frame-src` bypass via missing `src`](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-src)\n- [MDN on `<iframe>` `src`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-src)\n"
  },
  {
    "path": "docs/rules/template-require-iframe-title.md",
    "content": "# ember/template-require-iframe-title\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\n## `<iframe>`\n\n`<iframe>` elements must have a unique title property so assistive\ntechnology can convey their content to the user. The normative\nrequirement is [WCAG SC 4.1.2 (Name, Role, Value)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-rsv.html);\nthe `title` attribute is _one sufficient technique_ for meeting it\n(sufficient technique [H64](https://www.w3.org/WAI/WCAG21/Techniques/html/H64)).\n\n## Examples\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <iframe title='This is a unique title' />\n  <iframe title={{someValue}} />\n</template>\n```\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <iframe />\n  <iframe title='' />\n  <iframe title='   ' />\n  <iframe title={{null}} />\n  <iframe title={{undefined}} />\n  <iframe title={{true}} />\n  <iframe title={{false}} />\n  <iframe title={{42}} />\n</template>\n```\n\n## References\n\n- [WCAG SC 4.1.2 — Name, Role, Value](https://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-rsv.html)\n  — the normative requirement.\n- [WCAG Technique H64 — Using the title attribute of the iframe element](https://www.w3.org/WAI/WCAG21/Techniques/html/H64)\n  — a sufficient technique for SC 4.1.2, not itself normative.\n- [WCAG Success Criterion 2.4.1 — Bypass Blocks](https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-skip.html)\n- [ACCNAME 1.2 — accessible-name computation](https://www.w3.org/TR/accname-1.2/)\n- [axe-core rule `frame-title`](https://dequeuniversity.com/rules/axe/4.10/frame-title)\n"
  },
  {
    "path": "docs/rules/template-require-input-label.md",
    "content": "# ember/template-require-input-label\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nUsers with assistive technology need user-input form elements to have\nassociated labels.\n\nThe rule applies to the following HTML tags:\n\n- `<input>`\n- `<textarea>`\n- `<select>`\n\nThe rule also applies to the following ember components:\n\n- `<Textarea />`\n- `<Input />`\n- `{{textarea}}`\n- `{{input}}`\n\nThe label is **essential** for users. Leaving it out will cause **three**\ndifferent WCAG criteria to fail:\n\n- [1.3.1, Info and Relationships](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships.html)\n- [3.3.2, Labels or Instructions](https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html)\n- [4.1.2, Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html)\n\nIt is also associated with this common failure:\n\n- [#68: Failure of Success Criterion 4.1.2 due to a user interface control not having a programmatically determined name](https://www.w3.org/WAI/WCAG21/Techniques/failures/F68)\n\nThis rule checks to see if the input is contained by a label element. If it is\nnot, it checks to see if the input has any of these three attributes: `id`,\n`aria-label`, or `aria-labelledby`. While the `id` element on the input is not\na concrete indicator of the presence of an associated `<label>` element with a\n`for` attribute, it is a good indicator that one likely exists.\n\nThis rule does not allow an input to use a `title` attribute for a valid label.\nThis is because implementation by browsers is unreliable and incomplete.\n\nThis rule is unable to determine if a valid label is present if `...attributes`\nis used, and must allow it to pass. However, developers are encouraged to write\ntests to ensure that a valid label is present for each input element present.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div><input /></div>\n</template>\n```\n\n```gjs\n<template>\n  <input title=\"some label text\" />\n</template>\n```\n\n```gjs\n<template>\n  <textarea />\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <label>Some Label Text<input /></label>\n</template>\n```\n\n```gjs\n<template>\n  <input id=\"someId\" />\n</template>\n```\n\n```gjs\n<template>\n  <input aria-label=\"Label Text Here\" />\n</template>\n```\n\n```gjs\n<template>\n  <input aria-labelledby=\"someButtonId\" />\n</template>\n```\n\n```gjs\n<template>\n  <input ...attributes />\n</template>\n```\n\n```gjs\n<template>\n  <input type=\"hidden\" />\n</template>\n```\n\n## Migration\n\n- the recommended fix is to add an associated label element.\n- another option is to add an aria-label to the input element.\n- wrapping the input element in a label element is also allowed; however this is less flexible for styling purposes, so use with awareness.\n\n## Configuration\n\n- boolean - `true` to enable / `false` to disable\n- object -- An object with the following keys:\n  - `labelTags` -- An array of component names for that may be used as label replacements (in addition to the HTML `label` tag)\n\n## References\n\n- [Understanding Success Criterion 1.3.1: Info and Relationships](https://www.w3.org/WAI/WCAG21/Understanding/info-and-relationships)\n- [Understanding Success Criterion 3.3.2: Labels or Instructions](https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html)\n- [Understanding Success Criterion 4.1.2: Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html)\n- [Using label elements to associate text labels and form controls](https://www.w3.org/WAI/WCAG21/Techniques/html/H44.html)\n- [Using aria-labelledby to provide a name for user interface controls](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA16)\n- [Using aria-label to provide an invisible label where a visible label cannot be used](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA14.html)\n- [Failure due to a user interface control not having a programmatically determined name](https://www.w3.org/WAI/WCAG21/Techniques/failures/F68)\n- [Failure due to visually formatting a set of phone number fields but not including a text label](https://www.w3.org/WAI/WCAG21/Techniques/failures/F82)\n"
  },
  {
    "path": "docs/rules/template-require-input-type.md",
    "content": "# ember/template-require-input-type\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nThis rule rejects `<input type=\"...\">` values that are not one of the input\ntypes defined by the HTML spec, and (optionally) requires every `<input>` to\ndeclare a `type` attribute.\n\nAn invalid value like `<input type=\"foo\">` silently falls back to the Text\nstate — the browser reports no error, but the author's intent (validation,\ninputmode hint, platform keyboard) is lost. That's a genuine silent-failure\nclass, which this rule always flags and auto-fixes to `type=\"text\"`.\n\nA missing `type` attribute (`<input />`) is _spec-compliant_ — the\nmissing-value default is the Text state — so flagging it is a style /\nconsistency choice, not a correctness one. Opt in with `requireExplicit: true`\nif your team wants parity with `template-require-button-type`.\n\n## Examples\n\nThis rule **forbids** the following (always):\n\n```hbs\n<input type='' />\n<input type='foo' />\n<input type='TEXTY' />\n```\n\nWith `requireExplicit: true` the rule **also forbids**:\n\n```hbs\n<input />\n<input name='email' />\n```\n\nThis rule **allows** the following:\n\n```hbs\n<input type='text' />\n<input type='email' />\n<input type='checkbox' />\n<input type={{this.inputType}} />\n```\n\nDynamic values such as `type={{this.inputType}}` are not flagged at lint time.\n\n## Configuration\n\n- `requireExplicit` (`boolean`, default `false`): when true, also flag\n  `<input>` elements that have no `type` attribute. Auto-fix inserts\n  `type=\"text\"`.\n\n```js\nmodule.exports = {\n  rules: {\n    'ember/template-require-input-type': ['error', { requireExplicit: true }],\n  },\n};\n```\n\n## References\n\n- [HTML spec — the input element](https://html.spec.whatwg.org/multipage/input.html#the-input-element)\n- Adapted from [`html-validate`'s `no-implicit-input-type`](https://html-validate.org/rules/no-implicit-input-type.html) (MIT).\n"
  },
  {
    "path": "docs/rules/template-require-lang-attribute.md",
    "content": "# ember/template-require-lang-attribute\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nA missing or invalid `lang` attribute can cause an application to fail legal\nconformance for digital accessibility requirements.\n\nThis rule's objective is to ensure that Ember applications achieve [WCAG\nSuccess Criterion 3.1.1: Language of Page](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page.html),\nby declaring a valid IETF's BCP 47 language tag for the `lang` attribute. The\nstate of the `lang` attribute has a usability impact on the experience of users\nthat require screen-reading assistive technology. When the attribute is\nproperly assigned:\n\n> \"Both assistive technologies and conventional user agents can render text\n> more accurately when the language of the Web page is identified. Screen\n> readers can load the correct pronunciation rules. Visual browsers can display\n> characters and scripts correctly. Media players can show captions correctly.\n> **As a result, users with disabilities will be better able to understand the\n> content.**\"\n>\n> **Source: [WCAG Success Criterion 3.1.1:\n> Intent](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page.html#intent)**\n\nWhen the language of the page cannot be identified, the integrity of the above\ninformation cannot be guaranteed.\n\nConsider any of the following use cases:\n\n- the application developer is unaware that Ember now includes the lang attribute\n- the application does not require internationalization\n- the application's content is in a language that is not English\n- an end-user with a screen reader turned on, whose operating system (OS) is set to a different language, navigates to that page with their screen reader turned on\n- the screen reader would attempt to read the page in the language that is defined by the lang attribute on the page, but the supporting element information (\"button\", \"link\", etc) is read out in the language that is set by the operating system\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <html></html>\n</template>\n```\n\n```gjs\n<template>\n  <html lang=\"\"></html>\n</template>\n```\n\n```gjs\n<template>\n  <html lang=\"abracadabra\"></html>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <html lang=\"en\"></html>\n</template>\n```\n\n```gjs\n<template>\n  <html lang=\"en-US\"></html>\n</template>\n```\n\n```gjs\n<template>\n  <html lang={{lang}}></html>\n</template>\n```\n\n## Migration\n\nAdd the `lang` attribute to the `app/index.html` file in your Ember app. If\nyou use an internationalization addon like `ember-intl`, note that this will\nnot conflict with that addon.\n\n## Configuration\n\n- boolean -- if `true`, default configuration is applied\n- object -- containing the following property:\n  - boolean -- `validateValues` -- if `true`, the rule checks whether the value in the `lang` attribute is a known IETF's BCP 47 language tag (default: `true`)\n\n## References\n\n- [WCAG Success Criterion 3.1.1: Language of Page](https://www.w3.org/WAI/WCAG21/Understanding/language-of-page.html)\n"
  },
  {
    "path": "docs/rules/template-require-mandatory-role-attributes.md",
    "content": "# ember/template-require-mandatory-role-attributes\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nElements with ARIA roles must also include all required attributes for that\nrole. This ensures that a given element possesses the necessary states and\nproperties to behave consistently with user expectations for other elements\nwith the same ARIA role.\n\nThis rule enforces that elements with an ARIA role also declare all required\nARIA attributes for that role.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div role=\"option\" />\n  <CustomComponent role=\"checkbox\" aria-required=\"true\" />\n  {{some-component role=\"heading\"}}\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <div role=\"option\" aria-selected=\"false\" />\n  <CustomComponent role=\"checkbox\" aria-required=\"true\" aria-checked=\"false\" />\n  {{some-component role=\"heading\" aria-level=\"2\"}}\n\n  {{! Native inputs supply required ARIA state for matching roles. Lookup is\n      based on axobject-query's elementAXObjects + AXObjectRoles (see below). }}\n  <input type=\"checkbox\" role=\"switch\" />\n  <input type=\"checkbox\" role=\"checkbox\" />\n  <input type=\"radio\" role=\"radio\" />\n  <input type=\"range\" role=\"slider\" />\n</template>\n```\n\n## Semantic-role exemptions\n\nWhen the role attribute explicitly declares a role that the native element already provides, the native element supplies the required ARIA state and the rule does not flag missing attributes. The exemption is looked up via [axobject-query](https://github.com/A11yance/axobject-query)'s `elementAXObjects` + `AXObjectRoles` maps, matching the approach used by `eslint-plugin-jsx-a11y` and `@angular-eslint/template`.\n\nExempt pairings include (non-exhaustive):\n\n| Element                   | Role                 | Required ARIA state supplied by                                                                           |\n| ------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |\n| `<input type=\"checkbox\">` | `checkbox`, `switch` | native `checked` state                                                                                    |\n| `<input type=\"radio\">`    | `radio`              | native `checked` state                                                                                    |\n| `<input type=\"range\">`    | `slider`             | native `value` / `min` / `max`                                                                            |\n| `<input type=\"number\">`   | `spinbutton`         | native `value` / `min` / `max` (spinbutton's required `aria-valuenow` is derived from the native `value`) |\n| `<input type=\"text\">`     | `textbox`            | no required ARIA                                                                                          |\n| `<input type=\"search\">`   | `searchbox`          | no required ARIA                                                                                          |\n\nUndocumented pairings (e.g. `<input type=\"checkbox\" role=\"menuitemcheckbox\">` — axobject-query does not list this) remain flagged.\n\n## References\n\n- [WAI-ARIA Roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles)\n- [WAI-ARIA APG — Switch pattern](https://www.w3.org/WAI/ARIA/apg/patterns/switch/)\n- [HTML-AAM — `<input type=\"checkbox\">` → `checkbox` role mapping](https://www.w3.org/TR/html-aam-1.0/#el-input-checkbox)\n  — primary-spec source: HTML-AAM maps the native element to the\n  `checkbox` role and derives `aria-checked` from the element's\n  checkedness (and `indeterminate` for `mixed`). axobject-query\n  encodes that mapping for tooling.\n- [axobject-query](https://github.com/A11yance/axobject-query) — AX-tree data source for the exemption lookup (secondary, encodes HTML-AAM)\n"
  },
  {
    "path": "docs/rules/template-require-media-caption.md",
    "content": "# ember/template-require-media-caption\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nCaptions provide a text version of the spoken and non-spoken audio information\nfor media. They are essential for making audio and video content accessible for\nusers who are deaf as well as those for whom the media is unavailable (similar\nto `alt` text on an image when it is unable to load).\n\nCaptions should contain all relevant information needed to help users\nunderstand the media content, which may include a transcription of the dialogue\nand descriptions of meaningful sound effects. They are synchronized with the\nmedia to allow users access to the portion of the content conveyed via the\naudio track. Note that when audio or video components include the `muted`\nattribute, however, captions are _not_ necessary.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <audio></audio>\n</template>\n```\n\n```gjs\n<template>\n  <video><track /></video>\n</template>\n```\n\n```gjs\n<template>\n  <video><track kind=\"descriptions\" /></video>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <audio><track kind=\"captions\"></audio>\n</template>\n```\n\n```gjs\n<template>\n  <video muted=\"true\"></video>\n</template>\n```\n\n```gjs\n<template>\n  <video><track kind=\"captions\" /><track kind=\"descriptions\" /></video>\n</template>\n```\n\n## References\n\n- [Captions*Subtitles * Web Accessibility Initiative (WAI) \\_ W3C](https://www.w3.org/WAI/media/av/captions/)\n- [Understanding Success Criterion 1.2.2: Captions (Prerecorded)](https://www.w3.org/WAI/WCAG21/Understanding/captions-prerecorded.html)\n- [media-has-caption - eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/media-has-caption.md)\n"
  },
  {
    "path": "docs/rules/template-require-presentational-children.md",
    "content": "# ember/template-require-presentational-children\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThere are roles that require all children to be presentational. This rule checks\nif descendants of an element with one of those roles are presentational. By\ndefault, browsers are required to add `role=\"presentation\"` to all descendants,\nbut we should not rely on browsers to do this.\n\nThe roles that require all children to be presentational are:\n\n- `button`\n- `checkbox`\n- `img`\n- `meter`\n- `menuitemcheckbox`\n- `menuitemradio`\n- `option`\n- `progressbar`\n- `radio`\n- `scrollbar`\n- `separator`\n- `slider`\n- `switch`\n- `tab`\n\nPlease note that children of `<svg>` tags will not be checked by this rule, as\nthey have somewhat special semantics.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <li role=\"tab\"><h3>Title of My Tab</h3></li>\n</template>\n```\n\n```gjs\n<template>\n  <div role=\"button\">\n    <h2 role=\"presentation\">\n      <button>Test <img /></button>\n    </h2>\n  </div>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <li role=\"tab\">Title of My Tab</li>\n</template>\n```\n\n```gjs\n<template>\n  <li role=\"tab\"><h3 role=\"presentation\">Title of My Tab</h3></li>\n</template>\n```\n\n## Migration\n\nIf violations are found, remediation should be planned to either add\n`role=\"presentation\"` to the descendants as a quickfix. A better fix is to not\nuse semantic descendants.\n\n## Configuration\n\n- object -- An object with the following keys:\n  - `additionalNonSemanticTags` -- An array of additional tags that should be considered presentation\n\n```json\n{\n  \"ember/template-require-presentational-children\": [\n    \"error\",\n    {\n      \"additionalNonSemanticTags\": [\"my-custom-element\"]\n    }\n  ]\n}\n```\n\n## References\n\n- [Roles That Automatically Hide Semantics by Making Their Descendants Presentational](https://w3c.github.io/aria-practices/#children_presentational)\n"
  },
  {
    "path": "docs/rules/template-require-splattributes.md",
    "content": "# ember/template-require-splattributes\n\n<!-- end auto-generated rule header -->\n\nRequire splattributes usage in component templates.\n\nSplattributes (`...attributes`) make it possible to use attributes on component\ninvocations (e.g. `<SomeComponent class=\"blue\">`). Forgetting to add\n`...attributes` however makes it impossible to apply attributes like `class` to\na component.\n\nThis rule warns about templates that don't have `...attributes` in them.\n\nPlease note that this rule is only useful for Glimmer components or tagless\n(`tagName: ''`) classic components, because regular classic components have\nthis functionality built into the root element, which is not part of their\ntemplates.\n\nThis rule also should not be used for route/controller templates, because those\ndon't support `...attributes`. Instead of unconditionally enabling this rule in\nyour config, you might want to consider using overrides to only enable it for\ncomponent templates.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div>\n    component content\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <SomeOtherComponent>\n    component content\n  </SomeOtherComponent>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <div ...attributes>\n    component content\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <div class=\"foo\">\n    <SomeOtherComponent ...attributes />\n  </div>\n</template>\n```\n\n```js\nmodule.exports = {\n  extends: 'recommended',\n  rules: {\n    // ...\n  },\n  overrides: [\n    {\n      files: ['app/components/**/*.hbs'],\n      rules: { 'require-splattributes': 'error' },\n    },\n  ],\n};\n```\n\n## Migration\n\n- Add `...attributes` on at least one element or component invocation in the template (usually the root element)\n- Use `{{! template-lint-disable require-splattributes }}` where you explicitly don't want or need `...attributes`\n\n## Related Rules\n\n- [no-nested-splattributes](template-no-nested-splattributes.md)\n- [splat-attributes-only](template-splat-attributes-only.md)\n\n## References\n\n- [Splattributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/#toc_html-attributes) in the Ember.js guides\n"
  },
  {
    "path": "docs/rules/template-require-strict-mode.md",
    "content": "# ember/template-require-strict-mode\n\n> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.\n\n<!-- end auto-generated rule header -->\n\nRequire templates to be in strict mode.\n\nEmber's Polaris edition component authoring format is template tag, which makes\ntemplates follow \"strict mode\" semantics.\n\nThis rule requires all templates to use strict mode (template tag). Effectively this\nmeans you may only have template content in `.gjs`/`.gts` files, not in `.hbs` or\n`.js`/`.ts`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```hbs\n// button.hbs\n<button>{{yield}}</button>\n```\n\n```js\n// button-test.js\nimport { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n  await render(hbs`<Button>Ok</Button>`);\n  // ...\n});\n```\n\nThis rule **allows** the following:\n\n```gjs\n// button.gjs\n<template>\n  <button>{{yield}}</button>\n</template>\n```\n\n```gjs\n// button-test.gjs\nimport { Button } from 'ember-awesome-button';\n\ntest('it renders', async (assert) => {\n  await render(<template><Button>Ok</Button></template>);\n  // ..\n});\n```\n\n## References\n\n- [Template Tag Guide](https://guides.emberjs.com/release/components/template-tag-format/)\n- [Strict Mode RFC](https://rfcs.emberjs.com/id/0496-handlebars-strict-mode/)\n"
  },
  {
    "path": "docs/rules/template-require-valid-alt-text.md",
    "content": "# ember/template-require-valid-alt-text\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nEnforce that all elements that require alternative text have meaningful information to relay back to the end user. This is a critical component of accessibility for screenreader users in order for them to understand the content's purpose on the page. By default, this rule checks for alternative text on the following elements: `<img>`, `<area>`, `<input type=\"image\">`, and `<object>`.\n\nEnforce `img` alt attribute does not contain the word image, picture, or photo. Screen readers already announce `img` elements as an image. There is no need to use words such as _image_, _photo_, and/or _picture_. The rule will first check if `aria-hidden` is true to determine whether to enforce the rule. If the image is hidden, then rule will always succeed.\n\n## Examples\n\nThis rule **forbids** the following:\n\n### `<img>`\n\nAn `<img>` must have the `alt` attribute. It must have either meaningful text, or be an empty string.\n\nThe content of an `alt` attribute is used to calculate the machine-readable label of an element, whereas the text content is used to produce a label for the element. For this reason, adding a label to an icon can produce a confusing or duplicated label on a control that already has appropriate text content.\n\nIf it's not a meaningful image, it should have an empty alt attribute value and have the role of presentation or none.\n\n`img` alt attribute does not contain the word image, picture, or photo. Screen readers already announce `img` elements as an image. There is no need to use words such as _image_, _photo_, _logo_, _spacer_, and/or _picture_.\n\nNumbers are not considered valid alt text, and this rule disallows using only numbers in alt text.\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <img src='rwjblue.png' />\n  <img src='foo' alt='Photo of foo being weird.' />\n  <img src='foo' alt='YourCompany logo' />\n  <img src='bar' alt='Image of me at a bar!' />\n  <img src='baz' alt='Picture of baz fixing a bug.' />\n  <img src='b52.jpg' alt='52' />\n  <img src='foo' alt='foo as a banana' role='presentation' />\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <img\n    src='rwjblue.png'\n    alt='A man standing in front of a room of people, giving a presentation about Ember.'\n  />\n  <img src='foo' alt='YourCompany Home Page' />\n  <img src='bar' aria-hidden='true' alt='Picture of me taking a photo of an image' />\n  // Will pass because it is hidden.\n  <img src='baz' alt='Baz taking a {{photo}}' />\n  // This is valid since photo is a variable name.\n  <img src='b52.jpg' alt='b52 bomber jet' />\n  <img src='foo' alt='' role='presentation' />\n  // This is valid because it has a role of presentation.\n</template>\n```\n\n### `<object>`\n\nAdd alternative text to all embedded `<object>` elements using either inner text, setting the `title` prop, or using the `aria-label` or `aria-labelledby` props.\n\nNote, the `title` prop is generally less reliable than the alternatives. Some screen readers will not read this value aloud, leaving no description of the non-text content.\n\nThis rule **forbids** the following:\n\n```gjs\n<template><object width='128' height='256'></object></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <object width='128' height='256' title='Middle-sized'></object>\n  <object width='128' height='256' aria-label='Middle-sized'></object>\n  <object width='128' height='256' aria-labelledby='id-12345'></object>\n</template>\n```\n\n### `<input type=\"image\">`\n\nAll `<input type=\"image\">` elements must have a non-empty `alt` prop set with a meaningful description of the image or have the `aria-label` or `aria-labelledby` props set.\n\nThis rule **forbids** the following:\n\n```gjs\n<template><input type='image' /></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><input type='image' alt='Select image to upload' /></template>\n```\n\n### `<area>`\n\nAll clickable `<area>` elements within an image map have an `alt`, `aria-label` or `aria-labelledby` prop that describes the purpose of the link.\n\nThis rule **forbids** the following:\n\n```gjs\n<template><area shape='poly' coords='113,24,211,0' href='inform.html' /></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><area shape='poly' coords='113,24,211,0' href='inform.html' alt='Inform' /></template>\n```\n\n## References\n\n- [WCAG Technique- using alt attributes on img elements](https://www.w3.org/TR/WCAG20-TECHS/H37.html)\n- [WCAG Criterion 1.1.1 - Non-text Content](https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html)\n- [HTML 5.2 spec - the img element](https://www.w3.org/TR/html5/semantics-embedded-content.html#the-img-element)\n- [Failure of Success Criterion 1.1.1 due to providing a text alternative that is not null (e.g., alt=\"spacer\" or alt=\"image\") for images that should be ignored by assistive technology](https://www.w3.org/WAI/WCAG21/Techniques/failures/F39)\n"
  },
  {
    "path": "docs/rules/template-require-valid-form-groups.md",
    "content": "# ember/template-require-valid-form-groups\n\n<!-- end auto-generated rule header -->\n\nRequire grouped form controls to have appropriate semantics.\n\nThis rule requires appropriate semantics for grouped form controls. Correctly grouped\nform controls will take one of two approaches:\n\n- use `<fieldset>` + `<legend>` (preferred)\n- associate controls using WAI-ARIA (also acceptable)\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div>\n    <label for=\"radio-001\">Chicago Zoey</label>\n    <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\" />\n    <label for=\"radio-002\">Office Hours Tomster</label>\n    <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\" />\n    <label for=\"radio-003\">A11y Zoey</label>\n    <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\" />\n  </div>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <div role=\"group\" aria-labelledby=\"preferred-mascot-heading\">\n    <div id=\"preferred-mascot-heading\">Preferred Mascot Version</div>\n    <label for=\"radio-001\">Chicago Zoey</label>\n    <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\" />\n    <label for=\"radio-002\">Office Hours Tomster</label>\n    <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\" />\n    <label for=\"radio-003\">A11y Zoey</label>\n    <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\" />\n  </div>\n</template>\n```\n\n```gjs\n<template>\n  <fieldset>\n    <legend>Preferred Mascot Version</legend>\n      <div>\n        <label for=\"radio-001\">Chicago Zoey</label>\n        <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\" />\n      </div>\n      <div>\n        <label for=\"radio-002\">Office Hours Tomster</label>\n        <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\" />\n      </div>\n      <div>\n        <label for=\"radio-003\">A11y Zoey</label>\n        <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\" />\n      </div>\n  </fieldset>\n</template>\n```\n\n## References\n\n- [Grouping Controls](https://www.w3.org/WAI/tutorials/forms/grouping/)\n- [The Field Set element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)\n"
  },
  {
    "path": "docs/rules/template-require-valid-named-block-naming-format.md",
    "content": "# ember/template-require-valid-named-block-naming-format\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nRequire named blocks to use a valid naming format (`camelCase` or `kebab-case`).\n\nThe default naming format used is `camelCase`.\n\n## Examples\n\nThis rule **forbids** the following when the `camelCase` naming format is enabled:\n\n```gjs\n<template>\n  {{yield to=\"foo-bar\"}}\n  {{has-block \"foo-bar\"}}\n  {{if (has-block \"foo-bar\")}}\n  {{has-block-params \"foo-bar\"}}\n  {{if (has-block-params \"foo-bar\")}}\n</template>\n```\n\nThis rule **allows** the following when the `camelCase` naming format is enabled:\n\n```gjs\n<template>\n  {{yield to=\"fooBar\"}}\n  {{has-block \"fooBar\"}}\n  {{if (has-block \"fooBar\")}}\n  {{has-block-params \"fooBar\"}}\n  {{if (has-block-params \"fooBar\")}}\n</template>\n```\n\nThis rule **forbids** the following when the `kebab-case` naming format is enabled:\n\n```gjs\n<template>\n  {{yield to=\"fooBar\"}}\n  {{has-block \"fooBar\"}}\n  {{if (has-block \"fooBar\")}}\n  {{has-block-params \"fooBar\"}}\n  {{if (has-block-params \"fooBar\")}}\n</template>\n```\n\nThis rule **allows** the following when the `kebab-case` naming format is enabled:\n\n```gjs\n<template>\n  {{yield to=\"foo-bar\"}}\n  {{has-block \"foo-bar\"}}\n  {{if (has-block \"foo-bar\")}}\n  {{has-block-params \"foo-bar\"}}\n  {{if (has-block-params \"foo-bar\")}}\n</template>\n```\n\n## Configuration\n\n- boolean -- `true` enables the rule with the default `camelCase` format and `false`\n  disables it\n- string -- `\"camelCase\"` requires the use of the `camelCase` naming format, and\n  `\"kebab-case\"` requires the use of the `kebab-case` naming format\n\n## References\n\n- [Naming convention (programming)](<https://en.wikipedia.org/wiki/Naming_convention_(programming)>)\n"
  },
  {
    "path": "docs/rules/template-self-closing-void-elements.md",
    "content": "# ember/template-self-closing-void-elements\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nDisallow or require self-closing void elements.\n\nHTML has no self-closing tags. The HTML5 parser will ignore a self-closing marker on\n[void elements](https://html.spec.whatwg.org/#void-elements) (elements that should not\nhave a closing tag), but it is unnecessary and can be confusing when mixed with\nSVG/XML-like syntax.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <img src=\"http://emberjs.com/images/ember-logo.svg\" alt=\"ember\" />\n  <hr />\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <img src=\"http://emberjs.com/images/ember-logo.svg\" alt=\"ember\">\n  <hr>\n</template>\n```\n\nThere may be cases where a self-closing tag is preferred for void elements. In those\ncases, pass the string `\"require\"` to require the self-closing form instead.\n\n## Configuration\n\nThe following values are valid configuration:\n\n- boolean -- `true` for enabled / `false` for disabled\n- string -- `\"require\"` to mandate the use of self-closing tags\n\n## References\n\n- [HTML spec/void elements](https://html.spec.whatwg.org/#void-elements)\n"
  },
  {
    "path": "docs/rules/template-simple-modifiers.md",
    "content": "# ember/template-simple-modifiers\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nRequire simple modifier syntax.\n\nThis rule strongly advises against passing complex statements or conditionals to the\nfirst argument of the `{{modifier}}` helper. Instead, the first argument should be\neither:\n\n- a string literal such as `{{(modifier \"track-interaction\")}}`\n- a path expression such as `{{(modifier this.trackInteraction)}}`\n\nA common issue this rule catches is declaring the modifier name conditionally, which\nworks because `modifier` ignores `null` and `undefined`, but makes the intent much\nharder to read. Prefer placing the conditional around the modifier invocation instead.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template>\n  <div\n    {{(modifier\n      (unless this.hasBeenClicked 'track-interaction')\n      'click'\n      customizeData=this.customizeClickData\n    )}}\n  ></div>\n</template>\n```\n\n```gjs\n<template>\n  <div {{(modifier)}}></div>\n</template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template>\n  <div\n    {{(unless\n      this.hasBeenClicked\n      (modifier 'track-interaction' 'click' customizeData=this.customizeClickData)\n    )}}\n  ></div>\n</template>\n```\n\n## Why?\n\nUsing complex expressions as the modifier name reduces readability and makes it harder\nto understand which modifier is being applied.\n\n## References\n\n- [Ember.js Guides - Modifiers](https://guides.emberjs.com/release/components/template-lifecycle-dom-and-modifiers/#toc_event-handlers)\n"
  },
  {
    "path": "docs/rules/template-simple-unless.md",
    "content": "# ember/template-simple-unless\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nRequire simple conditions in `{{#unless}}` blocks. Complex expressions should use `{{#if}}` with negation instead.\n\n## Rule Details\n\nThis rule enforces using simple property paths in `{{#unless}}` blocks rather than complex helper expressions.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  {{#unless (or (eq a 1) (gt b 2))}}\n    Complex condition\n  {{/unless}}\n</template>\n```\n\n```gjs\n<template>\n  {{#unless (and isAdmin (not isBanned))}}\n    Not allowed\n  {{/unless}}\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  {{#unless isHidden}}\n    Visible\n  {{/unless}}\n</template>\n```\n\n```gjs\n<template>\n  {{#unless (eq value 1)}}\n    Not one\n  {{/unless}}\n</template>\n```\n\n```gjs\n<template>\n  {{#if (not (or a b))}}\n    Neither\n  {{/if}}\n</template>\n```\n\n## Options\n\n| Name         | Type       | Default | Description                                                                 |\n| ------------ | ---------- | ------- | --------------------------------------------------------------------------- |\n| `allowlist`  | `string[]` | `[]`    | Helper names allowed inside `{{unless}}`.                                   |\n| `denylist`   | `string[]` | `[]`    | Helper names explicitly denied inside `{{unless}}`.                         |\n| `maxHelpers` | `integer`  | `1`     | Maximum number of helpers allowed inside `{{unless}}` (`-1` for unlimited). |\n\n## Related Rules\n\n- [no-negated-condition](template-no-negated-condition.md)\n\n## References\n\n- [Wikipedia/boolean algebra](https://en.wikipedia.org/wiki/Boolean_algebra)\n"
  },
  {
    "path": "docs/rules/template-sort-invocations.md",
    "content": "# ember/template-sort-invocations\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\n## Why use it?\n\nThe rule helps you standardize templates:\n\n- Component invocations\n- Helper invocations\n- Modifier invocations\n\nBy sorting things that are order-independent, you can more easily refactor code. In addition, sorting removes style differences, so you can review another person's code more effectively.\n\n> [!TIP]\n>\n> The `--fix` option for this rule doesn't preserve formatting. You can use `prettier`, [`prettier-plugin-ember-hbs-tag`](https://github.com/ijlee2/prettier-plugin-ember-hbs-tag), and [`prettier-plugin-ember-template-tag`](https://github.com/ember-tooling/prettier-plugin-ember-template-tag) to format templates in `*.hbs`, `hbs` tags, and `<template>` tags, respectively.\n\n## Examples\n\n### Components\n\nWhen invoking a component, list things in the following order:\n\n1. Arguments\n2. Attributes\n3. Modifiers\n4. Splattributes\n\nThe order clearly shows how the component is customized more and more. Things are alphabetized within each group.\n\n```hbs\n<Ui::Button\n  @label='Submit form'\n  @type='submit'\n  data-test-button\n  {{on 'click' this.doSomething}}\n  ...attributes\n/>\n```\n\n> [!NOTE]\n>\n> In rare cases, the order of [`...attributes`](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/#toc_html-attributes) can matter. Similarly, the order can matter when an [ARIA attribute has multiple values](https://github.com/ijlee2/ember-container-query/issues/38#issuecomment-647017665).\n>\n> Disable the rule per instance in either case.\n\n### Helpers\n\nWhen invoking a helper, list the named arguments in alphabetical order.\n\n```hbs\n{{t\n  'my-component.description'\n  installedOn=this.installationDate\n  packageName='ember-source'\n  packageVersion='6.0.0'\n}}\n```\n\n### Modifiers\n\nSimilarly to helpers, list the named arguments in alphabetical order.\n\n## Limitations\n\nIt's intended that there are no options for sorting. Alphabetical sort is the simplest for everyone to understand and to apply across different projects. It's also the easiest to maintain.\n\nTo better meet your needs, consider creating a plugin for `ember-template-lint`.\n\n## Known issues\n\n1\\. If you passed an empty string as an argument's value, it has been replaced with `{{\"\"}}`. Let [`ember-template-lint`](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-unnecessary-curly-strings.md) fix the formatting change.\n\n```diff\n- <MyComponent @description={{\"\"}} />\n+ <MyComponent @description=\"\" />\n```\n\n2\\. Comments such as `{{! @glint-expect-error }}` may have shifted. Move them to the correct location.\n\n## References\n\nSource code and tests were copied from [`ember-codemod-sort-invocations`](https://github.com/ijlee2/ember-codemod-sort-invocations).\n"
  },
  {
    "path": "docs/rules/template-splat-attributes-only.md",
    "content": "# ember/template-splat-attributes-only\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nIt is easy to introduce typos when typing out `...attributes` or to use e.g.\n`...arguments` instead. Unfortunately, that leads to a cryptic runtime error,\nbut does not fail the build.\n\nThis rule warns you when you use an attribute starting with `...` that is **not**\n`...attributes`.\n\n## Examples\n\nThis rule **forbids** the following:\n\n```gjs\n<template><div ...atributes></div></template>\n```\n\n```gjs\n<template><div ...arguments></div></template>\n```\n\nThis rule **allows** the following:\n\n```gjs\n<template><div ...attributes></div></template>\n```\n\n## References\n\n- [Ember 3.11 release](https://blog.emberjs.com/2019/07/15/ember-3-11-released.html)\n"
  },
  {
    "path": "docs/rules/template-style-concatenation.md",
    "content": "# ember/template-style-concatenation\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nDisallows string concatenation in inline styles.\n\nString concatenation in style attributes can be error-prone and hard to maintain. Use the `{{html-safe}}` helper or a computed property instead.\n\n## Rule Details\n\nThis rule disallows string concatenation in `style` attributes.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div style=\"color: {{this.color}};\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div style={{concat \"width: \" this.width \"px;\"}}>Content</div>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div style=\"color: red;\">Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div style={{this.computedStyle}}>Content</div>\n</template>\n```\n\n```gjs\n<template>\n  <div style={{html-safe this.styleString}}>Content</div>\n</template>\n```\n\n## Migration\n\nIn your component:\n\n```js\nimport { htmlSafe } from '@ember/template';\n\nexport default class MyComponent extends Component {\n  get computedStyle() {\n    return htmlSafe(`width: ${this.width}px; color: ${this.color};`);\n  }\n}\n```\n\nThen in template:\n\n```gjs\n<template>\n  <div style={{this.computedStyle}}>Content</div>\n</template>\n```\n\n## Related Rules\n\n- [no-inline-styles](template-no-inline-styles.md)\n\n## References\n\n- [eslint-plugin-ember template-style-concatenation](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-style-concatenation.md)\n- [Ember.js Guides - htmlSafe](https://guides.emberjs.com/release/templates/writing-helpers/#toc_marking-strings-as-html-safe)\n"
  },
  {
    "path": "docs/rules/template-table-groups.md",
    "content": "# ember/template-table-groups\n\n💼 This rule is enabled in the 📋 `template-lint-migration` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nRequires table elements to use table grouping elements.\n\nTables should use `<thead>`, `<tbody>`, and `<tfoot>` elements to group related content. This improves accessibility for screen reader users and makes the table structure more semantic.\n\n## Rule Details\n\nThis rule requires that `<table>` elements use grouping elements (`<thead>`, `<tbody>`, `<tfoot>`) instead of having `<tr>` elements as direct children.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <table>\n    <tr><td>Data</td></tr>\n  </table>\n</template>\n```\n\n```gjs\n<template>\n  <table>\n    <tr><th>Header</th></tr>\n    <tr><td>Data</td></tr>\n  </table>\n</template>\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <table>\n    <thead>\n      <tr><th>Header</th></tr>\n    </thead>\n    <tbody>\n      <tr><td>Data</td></tr>\n    </tbody>\n  </table>\n</template>\n```\n\n```gjs\n<template>\n  <table>\n    <tbody>\n      <tr><td>Data</td></tr>\n    </tbody>\n  </table>\n</template>\n```\n\n## Options\n\n| Name                          | Type       | Default | Description                                    |\n| ----------------------------- | ---------- | ------- | ---------------------------------------------- |\n| `allowed-table-components`    | `string[]` | `[]`    | Component names treated as `<table>` elements. |\n| `allowed-caption-components`  | `string[]` | `[]`    | Component names treated as `<caption>`.        |\n| `allowed-colgroup-components` | `string[]` | `[]`    | Component names treated as `<colgroup>`.       |\n| `allowed-thead-components`    | `string[]` | `[]`    | Component names treated as `<thead>`.          |\n| `allowed-tbody-components`    | `string[]` | `[]`    | Component names treated as `<tbody>`.          |\n| `allowed-tfoot-components`    | `string[]` | `[]`    | Component names treated as `<tfoot>`.          |\n\n## References\n\n- [eslint-plugin-ember template-table-groups](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-table-groups.md)\n- [MDN - Table structure](https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Advanced)\n"
  },
  {
    "path": "docs/rules/template-template-length.md",
    "content": "# ember/template-template-length\n\n<!-- end auto-generated rule header -->\n\nEnforce template size constraints.\n\nVery long templates can indicate that markup should be split into smaller components.\nVery short templates can indicate that markup might be better inlined.\n\n## Config\n\nThis rule accepts either:\n\n- `true` / `false`\n- an object with:\n  - `max` (integer): maximum allowed template length in lines\n  - `min` (integer): minimum allowed template length in lines\n\nUsing `true` defaults to:\n\n- `max: 200`\n- `min: 5`\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```gjs\n<template>\n  <div>short</div>\n</template>\n```\n\nwith config:\n\n```json\n{ \"min\": 10 }\n```\n\nExamples of **correct** code for this rule:\n\n```gjs\n<template>\n  <div>line 1</div>\n  <div>line 2</div>\n  <div>line 3</div>\n</template>\n```\n\n## Related rules\n\n- [eslint/max-lines](https://eslint.org/docs/rules/max-lines)\n\n## References\n\n- [eslint-plugin-ember template-length](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-template-length.md)\n- [eslint/max-lines](https://eslint.org/docs/latest/rules/max-lines)\n"
  },
  {
    "path": "docs/rules/use-brace-expansion.md",
    "content": "# ember/use-brace-expansion\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n<!-- end auto-generated rule header -->\n\nThis allows much less redundancy and is easier to read.\n\nNote that **the dependent keys must be together (without space)** for the brace expansion to work.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nexport default Component.extend({\n  fullName: computed('user.firstName', 'user.lastName', {\n    // Code\n  }),\n});\n```\n\nExamples of **correct** code for this rule:\n\n```js\nexport default Component.extend({\n  fullName: computed('user.{firstName,lastName}', {\n    // Code\n  }),\n});\n```\n"
  },
  {
    "path": "docs/rules/use-ember-data-rfc-395-imports.md",
    "content": "# ember/use-ember-data-rfc-395-imports\n\n💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations).\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nUse &#34;Ember Data Packages&#34; from Ember RFC #395.\n\nThe goal of this rule is to ease the migration to the new @ember-data packages.\n\n## Context\n\nember-data has been split in multiple packages. For instance, its store is now released in \"@ember-data/store\" package. These packages have been released starting from ember-data version 3.11.\n\nFor TypeScript users, imports from `ember-data/types/registries/*` are still allowed since there is currently no equivalent in the new packages.\n\n## Examples\n\nExamples of **incorrect** code for this rule:\n\n```js\nimport Model from 'ember-data/model';\nimport attr from 'ember-data/attr';\n```\n\n```js\nimport DS from 'ember-data';\n\nconst { Model } = 'ember-data';\n```\n\nExamples of **correct** code for this rule:\n\n```js\nimport Model, { attr } from '@ember-data/model';\n```\n\n## How to fix\n\nThis rule implements a fix function (or to be more precise: it leverages the `no-old-shims` fix function). If, as a user, you can get rid of all the `import DS from \"ember-data\";` imports, you'll be able to use this rule fixer to complete the upgrade.\n\nNote that [a codemod is also available](https://github.com/ember-codemods/ember-data-codemod) to complete the upgrade.\n\n## When Not To Use It\n\nYou don't want to use this rule if you're using `ember-data@<3.11`.\n\n## Further Reading\n\n[Ember Data Packages RFC](https://github.com/emberjs/rfcs/blob/master/text/0395-ember-data-packages.md)\n"
  },
  {
    "path": "docs/rules/use-ember-get-and-set.md",
    "content": "# ember/use-ember-get-and-set\n\n🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).\n\n<!-- end auto-generated rule header -->\n\nUse `Ember.get` and `Ember.set`.\n\nThis way you don't have to worry whether the object that you're trying to access is an `Ember.Object` or not. It also solves the problem of trying to wrap every object in `Ember.Object` in order to be able to use things like `getWithDefault`.\n\nEmber tests use `this.get()` and `this.set()` as test helpers, so uses of them in the `tests` directory will not be reported.\nIn addition, all files in the `mirage` directory will be excluded from this rule.\n\nThis rule can be used with `eslint --fix` to automatically fix some occurrences.\nTo be auto-fixed, `Ember` must be imported.\nIdeally, you should also be using [new-module-imports](./new-module-imports.md); otherwise, the fixed code will look like `Ember.get(this, fooProperty')` instead of `get(this, 'fooProperty')`.\n\n## Examples\n\n```js\n// Not recommended\nthis.get('fooProperty');\nthis.set('fooProperty', 'bar');\nthis.getWithDefault('fooProperty', 'defaultProp');\nobject.get('fooProperty');\nobject.getProperties('foo', 'bar');\nobject.setProperties({ foo: 'bar', baz: 'qux' });\n```\n\n```js\n// Recommended\nimport { get, set, getWithDefault, getProperties, setProperties } from '@ember/object';\n\n// ...\n\nget(this, 'fooProperty');\nset(this, 'fooProperty', 'bar');\ngetWithDefault(this, 'fooProperty', 'defaultProp');\nget(object, 'fooProperty');\ngetProperties(object, 'foo', 'bar');\nsetProperties(object, { foo: 'bar', baz: 'qux' });\n```\n\n## Configuration\n\n<!-- begin auto-generated rule options list -->\n\n| Name                       | Description                                                                                                        | Type    | Default |\n| :------------------------- | :----------------------------------------------------------------------------------------------------------------- | :------ | :------ |\n| `ignoreNonThisExpressions` | Enabling allows use of non-Ember objects like `server.get()` and `map.set()`.                                      | Boolean | `false` |\n| `ignoreThisExpressions`    | Enabling allows use of `this.get()` and `this.set()` where you will generally know if `this` is an `Ember.Object`. | Boolean | `false` |\n\n<!-- end auto-generated rule options list -->\n"
  },
  {
    "path": "eslint-remote-tester.config.js",
    "content": "/* eslint filenames/match-regex:off */\nconst fs = require('fs');\n\n/** @type {import('eslint-remote-tester').Config} */\nmodule.exports = {\n  /** Repositories to scan */\n  repositories: [\n    // Several dozen top Ember apps, addons, and engines.\n    'DockYard/ember-composable-helpers',\n    'adopted-ember-addons/ember-cli-flash',\n    'adopted-ember-addons/ember-cli-sass',\n    'adopted-ember-addons/ember-cp-validations',\n    'adopted-ember-addons/ember-data-model-fragments',\n    'adopted-ember-addons/ember-electron',\n    'adopted-ember-addons/ember-moment',\n    'babel/ember-cli-babel',\n    'cibernox/ember-power-select',\n    'ef4/ember-auto-import',\n    'ember-cli/ember-cli',\n    'ember-cli/ember-cli-htmlbars',\n    'ember-decorators/ember-decorators',\n    'emberjs/data',\n    'emberjs/ember-qunit',\n    'emberjs/ember.js',\n    'jmurphyau/ember-truth-helpers',\n    'machty/ember-concurrency',\n    'miragejs/ember-cli-mirage',\n    'typed-ember/ember-cli-typescript',\n  ],\n\n  /** Extensions of files under scanning */\n  extensions: ['js', 'ts'],\n\n  /** Optional boolean flag used to enable caching of cloned repositories. For CIs it's ideal to disable caching. Defaults to true. */\n  cache: false,\n\n  /** ESLint configuration */\n  eslintrc: {\n    plugins: ['ember'],\n\n    // Enable all of our rules.\n    rules: Object.fromEntries(\n      fs\n        .readdirSync(`${__dirname}/lib/rules`)\n        .map((filename) => `ember/${filename.replace(/\\.js$/, '')}`)\n        .map((ruleName) => {\n          let value;\n          // A few rules require additional configuration.\n          if (ruleName === 'ember/no-restricted-property-modifications') {\n            value = ['error', { properties: ['myProp'] }];\n          } else if (ruleName === 'ember/no-restricted-service-injections') {\n            value = ['error', { services: ['my-service'] }];\n          } else {\n            value = 'error';\n          }\n          return [ruleName, value];\n        })\n    ),\n\n    overrides: [\n      {\n        files: ['*.ts'],\n        parser: '@typescript-eslint/parser',\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "eslint.config.js",
    "content": "'use strict';\n/* eslint filenames/match-regex:\"off\" */\n\nconst js = require('@eslint/js');\nconst babelEslintParser = require('@babel/eslint-parser');\nconst eslintPluginEslintPluginAll = require('eslint-plugin-eslint-plugin/configs/all');\nconst eslintPluginFilenames = require('eslint-plugin-filenames');\nconst eslintPluginMarkdown = require('eslint-plugin-markdown');\nconst eslintPluginN = require('eslint-plugin-n');\n\nconst eslintPluginUnicorn = require('eslint-plugin-unicorn');\nconst eslintConfigPrettier = require('eslint-config-prettier');\nconst globals = require('globals');\nconst { FlatCompat } = require('@eslint/eslintrc');\nconst { includeIgnoreFile } = require('@eslint/compat');\nconst path = require('node:path');\n\nconst gitignorePath = path.resolve(__dirname, '.gitignore');\n\nconst compat = new FlatCompat({\n  baseDirectory: __dirname,\n  recommendedConfig: js.configs.recommended,\n});\n\nmodule.exports = [\n  // Legacy configs:\n  ...compat.extends(\n    'plugin:eslint-comments/recommended',\n    'plugin:import/errors',\n    'plugin:import/warnings',\n    'prettier'\n  ),\n\n  // Flat configs:\n  eslintPluginEslintPluginAll,\n  eslintPluginN.configs['flat/recommended'],\n  eslintPluginUnicorn.configs['flat/recommended'],\n  eslintConfigPrettier,\n\n  {\n    languageOptions: {\n      parser: babelEslintParser,\n      sourceType: 'script',\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'script',\n        babelOptions: {\n          configFile: require.resolve('./.babelrc'),\n        },\n      },\n    },\n    plugins: { filenames: eslintPluginFilenames },\n    rules: {\n      // Optional eslint rules:\n      'array-callback-return': 'error',\n      'block-scoped-var': 'error',\n      'consistent-return': 'error',\n      'default-case': 'error',\n      'default-case-last': 'error',\n      'new-parens': 'error',\n      'no-async-promise-executor': 'error',\n      'no-console': 'error',\n      'no-duplicate-imports': 'error',\n      'no-eval': 'error',\n      'no-extend-native': 'error',\n      'no-extra-bind': 'error',\n      'no-implicit-coercion': 'error',\n      'no-implied-eval': 'error',\n      'no-lone-blocks': 'error',\n      'no-multiple-empty-lines': 'error',\n      'no-new-func': 'error',\n      'no-new-wrappers': 'error',\n      'no-octal-escape': 'error',\n      'no-param-reassign': ['error', { props: true }],\n      'no-return-assign': 'error',\n      'no-return-await': 'error',\n      'no-self-compare': 'error',\n      'no-sequences': 'error',\n      'no-shadow-restricted-names': 'error',\n      'no-template-curly-in-string': 'error',\n      'no-throw-literal': 'error',\n      'no-unused-expressions': 'error',\n      'no-use-before-define': ['error', 'nofunc'],\n      'no-useless-backreference': 'error',\n      'no-useless-call': 'error',\n      'no-useless-catch': 'error',\n      'no-useless-computed-key': 'error',\n      'no-useless-concat': 'error',\n      'no-useless-constructor': 'error',\n      'no-useless-escape': 'error',\n      'no-useless-rename': 'error',\n      'no-useless-return': 'error',\n      'no-var': 'error',\n      'no-void': 'error',\n      'no-with': 'error',\n      'object-shorthand': 'error',\n      'prefer-const': 'error',\n      'prefer-numeric-literals': 'error',\n      'prefer-promise-reject-errors': 'error',\n      'prefer-rest-params': 'error',\n      'prefer-spread': 'error',\n      'prefer-template': 'error',\n      'require-atomic-updates': 'error',\n      'require-await': 'error',\n      'spaced-comment': ['error', 'always', { exceptions: ['-'] }],\n      'wrap-iife': 'error',\n      complexity: 'error',\n      curly: 'error',\n      eqeqeq: 'error',\n      quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }], // Disallow unnecessary template literals.\n      radix: 'error',\n      yoda: 'error',\n      // eslint-comments rules:\n      'eslint-comments/no-unused-disable': 'error',\n\n      // eslint-plugin rules:\n      'eslint-plugin/consistent-output': ['error', 'always'],\n      'eslint-plugin/prefer-message-ids': 'off',\n      'eslint-plugin/require-meta-docs-url': [\n        'error',\n        {\n          pattern:\n            'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/{{name}}.md',\n        },\n      ],\n\n      // Filenames:\n      'filenames/match-regex': ['error', '^.?[a-z0-9-]+$'], // Kebab-case.\n\n      // Optional import rules:\n      'import/extensions': 'error',\n      'import/first': 'error',\n      'import/newline-after-import': 'error',\n      'import/no-absolute-path': 'error',\n      'import/no-cycle': 'error',\n      'import/no-deprecated': 'error',\n      'import/no-dynamic-require': 'error',\n      'import/no-mutable-exports': 'error',\n      'import/no-named-default': 'error',\n      'import/no-self-import': 'error',\n      'import/no-unassigned-import': 'error',\n      'import/no-unused-modules': 'error',\n      'import/no-useless-path-segments': 'error',\n      'import/no-webpack-loader-syntax': 'error',\n      'import/unambiguous': 'error',\n\n      'n/no-unsupported-features/node-builtins': 'off',\n\n      // Unicorn rules:\n      'unicorn/no-array-callback-reference': 'off',\n      'unicorn/no-array-method-this-argument': 'off',\n      'unicorn/no-array-reduce': 'off',\n      'unicorn/no-lonely-if': 'off',\n      'unicorn/no-null': 'off',\n      'unicorn/no-useless-undefined': 'off',\n      'unicorn/prefer-module': 'off',\n      'unicorn/prefer-node-protocol': 'off', // Enable once we drop support for Node 14.0.0.\n      'unicorn/prevent-abbreviations': 'off',\n    },\n  },\n  {\n    // Test files:\n    files: ['**/*.js', '**/*.js.snap'],\n    plugins: {},\n    languageOptions: {\n      globals: {\n        ...globals.jest,\n      },\n    },\n  },\n  {\n    files: ['**/*.md'],\n    plugins: { markdown: eslintPluginMarkdown },\n    processor: 'markdown/markdown',\n  },\n  {\n    // Markdown code samples in documentation:\n    files: ['**/*.md/*.js'],\n    languageOptions: {\n      sourceType: 'module',\n      parserOptions: {\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n    rules: {\n      // Ignore violations that generally don't matter inside code samples.\n      'filenames/match-regex': 'off',\n      'import/no-unresolved': 'off',\n      'import/unambiguous': 'off',\n      'n/no-extraneous-import': 'off',\n      'n/no-missing-import': 'off',\n      'n/no-missing-require': 'off',\n      'n/no-unsupported-features/es-syntax': 'off',\n      'no-console': 'off',\n      'no-undef': 'off',\n      'no-unused-expressions': 'off',\n      'no-unused-labels': 'off',\n      'no-unused-vars': 'off',\n      'no-useless-constructor': 'off',\n      'unicorn/consistent-function-scoping': 'off',\n      'unicorn/filename-case': 'off',\n    },\n  },\n  {\n    files: ['**/*.mjs'],\n    languageOptions: {\n      parser: babelEslintParser,\n      parserOptions: {\n        ecmaFeatures: { modules: true },\n        ecmaVersion: 2022,\n        babelOptions: {\n          configFile: require.resolve('./.babelrc'),\n        },\n      },\n    },\n    rules: {\n      // Extensions are required in ESM\n      'import/extensions': ['error', 'ignorePackages'],\n      // These don't appear to work correctly\n      // All these throw on the import of a dependency\n      'import/namespace': 'off',\n      'import/no-deprecated': 'off',\n      'import/default': 'off',\n      'import/no-named-as-default': 'off',\n      'import/no-named-as-default-member': 'off',\n      'import/no-unresolved': 'off',\n      'import/no-missing-import': 'off',\n\n      // vite config format does not match regex\n      'filenames/match-regex': 'off',\n    },\n  },\n  {\n    ignores: [\n      'coverage/',\n      'node_modules/',\n      'lib/recommended-rules.js',\n      'lib/recommended-rules-gjs.js',\n      'lib/recommended-rules-gts.js',\n      'tests/__snapshots__/',\n      'tests/bench/',\n      'tests/lint.bench.mjs',\n\n      // # Contains <template> in js markdown\n      'docs/rules/no-empty-glimmer-component-classes.md',\n      'docs/rules/template-no-let-reference.md',\n    ],\n  },\n  includeIgnoreFile(gitignorePath, 'Imported .gitignore patterns'),\n];\n"
  },
  {
    "path": "lib/config/base.js",
    "content": "const plugin = require('../index');\nconst emberEslintParser = require('ember-eslint-parser');\n\nmodule.exports = [\n  {\n    plugins: { ember: plugin },\n  },\n\n  /**\n   * We don't want to *always* have the preprocessor active,\n   * it's only relevant on gjs and gts files to detect if eslint config is correctly setup for this files.\n   */\n  {\n    files: ['**/*.{gts,gjs}'],\n    languageOptions: {\n      parser: emberEslintParser,\n    },\n    processor: 'ember/noop',\n  },\n];\n"
  },
  {
    "path": "lib/config/recommended-gjs.js",
    "content": "const base = require('./base');\nconst gjsRules = require('../recommended-rules-gjs');\n\nmodule.exports = [...base, { rules: gjsRules }];\n"
  },
  {
    "path": "lib/config/recommended-gts.js",
    "content": "const base = require('./base');\nconst gtsRules = require('../recommended-rules-gts');\n\n// see https://github.com/typescript-eslint/typescript-eslint/issues/8607\n// https://github.com/typescript-eslint/typescript-eslint/blob/v7.4.0/packages/eslint-plugin/src/configs/eslint-recommended-raw.ts\nconst tsRecommended = {\n  'constructor-super': 'off', // ts(2335) & ts(2377)\n  'getter-return': 'off', // ts(2378)\n  'no-const-assign': 'off', // ts(2588)\n  'no-dupe-args': 'off', // ts(2300)\n  'no-dupe-class-members': 'off', // ts(2393) & ts(2300)\n  'no-dupe-keys': 'off', // ts(1117)\n  'no-func-assign': 'off', // ts(2630)\n  'no-import-assign': 'off', // ts(2632) & ts(2540)\n  'no-new-symbol': 'off', // ts(7009)\n  'no-obj-calls': 'off', // ts(2349)\n  'no-redeclare': 'off', // ts(2451)\n  'no-setter-return': 'off', // ts(2408)\n  'no-this-before-super': 'off', // ts(2376) & ts(17009)\n  'no-undef': 'off', // ts(2304) & ts(2552)\n  'no-unreachable': 'off', // ts(7027)\n  'no-unsafe-negation': 'off', // ts(2365) & ts(2322) & ts(2358)\n  'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more\n  'prefer-const': 'error', // ts provides better types with const\n  'prefer-rest-params': 'error', // ts provides better types with rest args over arguments\n  'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply\n};\n\nmodule.exports = [...base, { rules: { ...gtsRules, ...tsRecommended } }];\n"
  },
  {
    "path": "lib/config/recommended.js",
    "content": "const base = require('./base');\nconst rules = require('../recommended-rules');\n\nmodule.exports = [...base, { rules }];\n"
  },
  {
    "path": "lib/config/template-lint-migration.js",
    "content": "const base = require('./base');\n\nmodule.exports = [\n  ...base,\n  {\n    rules: {\n      'ember/template-builtin-component-arguments': 'error',\n      'ember/template-deprecated-inline-view-helper': 'error',\n      'ember/template-deprecated-render-helper': 'error',\n      'ember/template-link-href-attributes': 'error',\n      'ember/template-link-rel-noopener': 'error',\n      'ember/template-no-abstract-roles': 'error',\n      'ember/template-no-accesskey-attribute': 'error',\n      'ember/template-no-action': 'error',\n      'ember/template-no-action-on-submit-button': 'error',\n      'ember/template-no-args-paths': 'error',\n      'ember/template-no-arguments-for-html-elements': 'error',\n      'ember/template-no-aria-hidden-body': 'error',\n      'ember/template-no-aria-unsupported-elements': 'error',\n      'ember/template-no-array-prototype-extensions': 'error',\n      'ember/template-no-at-ember-render-modifiers': 'error',\n      'ember/template-no-attrs-in-components': 'error',\n      'ember/template-no-autofocus-attribute': 'error',\n      'ember/template-no-block-params-for-html-elements': 'error',\n      'ember/template-no-builtin-form-components': 'error',\n      'ember/template-no-capital-arguments': 'error',\n      'ember/template-no-class-bindings': 'error',\n      'ember/template-no-curly-component-invocation': 'error',\n      'ember/template-no-debugger': 'error',\n      'ember/template-no-duplicate-attributes': 'error',\n      'ember/template-no-duplicate-id': 'error',\n      'ember/template-no-duplicate-landmark-elements': 'error',\n      'ember/template-no-empty-headings': 'error',\n      'ember/template-no-extra-mut-helper-argument': 'error',\n      'ember/template-no-forbidden-elements': 'error',\n      'ember/template-no-heading-inside-button': 'error',\n      'ember/template-no-html-comments': 'error',\n      'ember/template-no-implicit-this': 'error',\n      'ember/template-no-index-component-invocation': 'error',\n      'ember/template-no-inline-styles': 'error',\n      'ember/template-no-input-block': 'error',\n      'ember/template-no-input-tagname': 'error',\n      'ember/template-no-invalid-aria-attributes': 'error',\n      'ember/template-no-invalid-interactive': 'error',\n      'ember/template-no-invalid-link-text': 'error',\n      'ember/template-no-invalid-link-title': 'error',\n      'ember/template-no-invalid-meta': 'error',\n      'ember/template-no-invalid-role': 'error',\n      'ember/template-no-link-to-positional-params': 'error',\n      'ember/template-no-link-to-tagname': 'error',\n      'ember/template-no-log': 'error',\n      'ember/template-no-negated-condition': 'error',\n      'ember/template-no-nested-interactive': 'error',\n      'ember/template-no-nested-landmark': 'error',\n      'ember/template-no-nested-splattributes': 'error',\n      'ember/template-no-obscure-array-access': 'error',\n      'ember/template-no-obsolete-elements': 'error',\n      'ember/template-no-outlet-outside-routes': 'error',\n      'ember/template-no-passed-in-event-handlers': 'error',\n      'ember/template-no-pointer-down-event-binding': 'error',\n      'ember/template-no-positional-data-test-selectors': 'error',\n      'ember/template-no-positive-tabindex': 'error',\n      'ember/template-no-potential-path-strings': 'error',\n      'ember/template-no-quoteless-attributes': 'error',\n      'ember/template-no-redundant-fn': 'error',\n      'ember/template-no-redundant-role': 'error',\n      'ember/template-no-route-action': 'error',\n      'ember/template-no-scope-outside-table-headings': 'error',\n      'ember/template-no-shadowed-elements': 'error',\n      'ember/template-no-triple-curlies': 'error',\n      'ember/template-no-unbalanced-curlies': 'error',\n      'ember/template-no-unbound': 'error',\n      'ember/template-no-unknown-arguments-for-builtin-components': 'error',\n      'ember/template-no-unnecessary-component-helper': 'error',\n      'ember/template-no-unnecessary-curly-parens': 'error',\n      'ember/template-no-unnecessary-curly-strings': 'error',\n      'ember/template-no-unsupported-role-attributes': 'error',\n      'ember/template-no-unused-block-params': 'error',\n      'ember/template-no-valueless-arguments': 'error',\n      'ember/template-no-whitespace-for-layout': 'error',\n      'ember/template-no-whitespace-within-word': 'error',\n      'ember/template-no-with': 'error',\n      'ember/template-no-yield-only': 'error',\n      'ember/template-no-yield-to-default': 'error',\n      'ember/template-require-aria-activedescendant-tabindex': 'error',\n      'ember/template-require-button-type': 'error',\n      'ember/template-require-context-role': 'error',\n      'ember/template-require-has-block-helper': 'error',\n      'ember/template-require-iframe-title': 'error',\n      'ember/template-require-input-label': 'error',\n      'ember/template-require-lang-attribute': 'error',\n      'ember/template-require-mandatory-role-attributes': 'error',\n      'ember/template-require-media-caption': 'error',\n      'ember/template-require-presentational-children': 'error',\n      'ember/template-require-valid-alt-text': 'error',\n      'ember/template-require-valid-named-block-naming-format': 'error',\n      'ember/template-simple-modifiers': 'error',\n      'ember/template-simple-unless': 'error',\n      'ember/template-splat-attributes-only': 'error',\n      'ember/template-style-concatenation': 'error',\n      'ember/template-table-groups': 'error',\n    },\n  },\n];\n"
  },
  {
    "path": "lib/config-legacy/base.js",
    "content": "module.exports = {\n  root: true,\n\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n\n  env: {\n    browser: true,\n    es2022: true,\n  },\n\n  plugins: ['ember'],\n\n  overrides: [\n    /**\n     * We don't want to *always* have the preprocessor active,\n     * it's only relevant on gjs and gts files to detect if eslint config is correctly setup for this files.\n     */\n    {\n      files: ['**/*.{gts,gjs}'],\n      parser: 'ember-eslint-parser',\n      processor: 'ember/noop',\n    },\n  ],\n};\n"
  },
  {
    "path": "lib/config-legacy/recommended-gjs.js",
    "content": "const base = require('./base');\nconst gjsRules = require('../recommended-rules-gjs');\n\nmodule.exports = {\n  ...base,\n  rules: gjsRules,\n};\n"
  },
  {
    "path": "lib/config-legacy/recommended-gts.js",
    "content": "const base = require('./base');\nconst gtsRules = require('../recommended-rules-gts');\n\nmodule.exports = {\n  ...base,\n  rules: gtsRules,\n};\n"
  },
  {
    "path": "lib/config-legacy/recommended.js",
    "content": "const base = require('./base');\nconst rules = require('../recommended-rules');\n\nmodule.exports = {\n  ...base,\n  rules,\n};\n"
  },
  {
    "path": "lib/config-legacy/template-lint-migration.js",
    "content": "'use strict';\n\n// Legacy (pre-ESLint 9) version of the template-lint-migration config.\n// For the flat config equivalent, see lib/config/template-lint-migration.js.\nconst base = require('./base');\n\nmodule.exports = {\n  ...base,\n  rules: {\n    'ember/template-builtin-component-arguments': 'error',\n    'ember/template-deprecated-inline-view-helper': 'error',\n    'ember/template-deprecated-render-helper': 'error',\n    'ember/template-link-href-attributes': 'error',\n    'ember/template-link-rel-noopener': 'error',\n    'ember/template-no-abstract-roles': 'error',\n    'ember/template-no-accesskey-attribute': 'error',\n    'ember/template-no-action': 'error',\n    'ember/template-no-action-on-submit-button': 'error',\n    'ember/template-no-args-paths': 'error',\n    'ember/template-no-arguments-for-html-elements': 'error',\n    'ember/template-no-aria-hidden-body': 'error',\n    'ember/template-no-aria-unsupported-elements': 'error',\n    'ember/template-no-array-prototype-extensions': 'error',\n    'ember/template-no-at-ember-render-modifiers': 'error',\n    'ember/template-no-attrs-in-components': 'error',\n    'ember/template-no-autofocus-attribute': 'error',\n    'ember/template-no-block-params-for-html-elements': 'error',\n    'ember/template-no-builtin-form-components': 'error',\n    'ember/template-no-capital-arguments': 'error',\n    'ember/template-no-class-bindings': 'error',\n    'ember/template-no-curly-component-invocation': 'error',\n    'ember/template-no-debugger': 'error',\n    'ember/template-no-duplicate-attributes': 'error',\n    'ember/template-no-duplicate-id': 'error',\n    'ember/template-no-duplicate-landmark-elements': 'error',\n    'ember/template-no-empty-headings': 'error',\n    'ember/template-no-extra-mut-helper-argument': 'error',\n    'ember/template-no-forbidden-elements': 'error',\n    'ember/template-no-heading-inside-button': 'error',\n    'ember/template-no-html-comments': 'error',\n    'ember/template-no-implicit-this': 'error',\n    'ember/template-no-index-component-invocation': 'error',\n    'ember/template-no-inline-styles': 'error',\n    'ember/template-no-input-block': 'error',\n    'ember/template-no-input-tagname': 'error',\n    'ember/template-no-invalid-aria-attributes': 'error',\n    'ember/template-no-invalid-interactive': 'error',\n    'ember/template-no-invalid-link-text': 'error',\n    'ember/template-no-invalid-link-title': 'error',\n    'ember/template-no-invalid-meta': 'error',\n    'ember/template-no-invalid-role': 'error',\n    'ember/template-no-link-to-positional-params': 'error',\n    'ember/template-no-link-to-tagname': 'error',\n    'ember/template-no-log': 'error',\n    'ember/template-no-negated-condition': 'error',\n    'ember/template-no-nested-interactive': 'error',\n    'ember/template-no-nested-landmark': 'error',\n    'ember/template-no-nested-splattributes': 'error',\n    'ember/template-no-obscure-array-access': 'error',\n    'ember/template-no-obsolete-elements': 'error',\n    'ember/template-no-outlet-outside-routes': 'error',\n    'ember/template-no-passed-in-event-handlers': 'error',\n    'ember/template-no-pointer-down-event-binding': 'error',\n    'ember/template-no-positional-data-test-selectors': 'error',\n    'ember/template-no-positive-tabindex': 'error',\n    'ember/template-no-potential-path-strings': 'error',\n    'ember/template-no-quoteless-attributes': 'error',\n    'ember/template-no-redundant-fn': 'error',\n    'ember/template-no-redundant-role': 'error',\n    'ember/template-no-route-action': 'error',\n    'ember/template-no-scope-outside-table-headings': 'error',\n    'ember/template-no-shadowed-elements': 'error',\n    'ember/template-no-triple-curlies': 'error',\n    'ember/template-no-unbalanced-curlies': 'error',\n    'ember/template-no-unbound': 'error',\n    'ember/template-no-unknown-arguments-for-builtin-components': 'error',\n    'ember/template-no-unnecessary-component-helper': 'error',\n    'ember/template-no-unnecessary-curly-parens': 'error',\n    'ember/template-no-unnecessary-curly-strings': 'error',\n    'ember/template-no-unsupported-role-attributes': 'error',\n    'ember/template-no-unused-block-params': 'error',\n    'ember/template-no-valueless-arguments': 'error',\n    'ember/template-no-whitespace-for-layout': 'error',\n    'ember/template-no-whitespace-within-word': 'error',\n    'ember/template-no-with': 'error',\n    'ember/template-no-yield-only': 'error',\n    'ember/template-no-yield-to-default': 'error',\n    'ember/template-require-aria-activedescendant-tabindex': 'error',\n    'ember/template-require-button-type': 'error',\n    'ember/template-require-context-role': 'error',\n    'ember/template-require-has-block-helper': 'error',\n    'ember/template-require-iframe-title': 'error',\n    'ember/template-require-input-label': 'error',\n    'ember/template-require-lang-attribute': 'error',\n    'ember/template-require-mandatory-role-attributes': 'error',\n    'ember/template-require-media-caption': 'error',\n    'ember/template-require-presentational-children': 'error',\n    'ember/template-require-valid-alt-text': 'error',\n    'ember/template-require-valid-named-block-naming-format': 'error',\n    'ember/template-simple-modifiers': 'error',\n    'ember/template-simple-unless': 'error',\n    'ember/template-splat-attributes-only': 'error',\n    'ember/template-style-concatenation': 'error',\n    'ember/template-table-groups': 'error',\n  },\n};\n"
  },
  {
    "path": "lib/index.js",
    "content": "'use strict';\n\nconst requireIndex = require('requireindex');\nconst noop = require('ember-eslint-parser/noop');\nconst pkg = require('../package.json'); // eslint-disable-line import/extensions\n\nmodule.exports = {\n  meta: {\n    name: pkg.name,\n    version: pkg.version,\n  },\n  rules: requireIndex(`${__dirname}/rules`),\n  configs: requireIndex(`${__dirname}/config-legacy`),\n  utils: {\n    ember: require('./utils/ember'),\n  },\n  processors: {\n    // https://eslint.org/docs/developer-guide/working-with-plugins#file-extension-named-processor\n    noop,\n  },\n};\n"
  },
  {
    "path": "lib/recommended-rules-gjs.js",
    "content": "/*\n * IMPORTANT!\n * This file has been automatically generated.\n * In order to update its content based on rules'\n * definitions, execute \"npm run update\"\n */\nmodule.exports = {\n  'ember/template-no-let-reference': 'error',\n};\n"
  },
  {
    "path": "lib/recommended-rules-gts.js",
    "content": "/*\n * IMPORTANT!\n * This file has been automatically generated.\n * In order to update its content based on rules'\n * definitions, execute \"npm run update\"\n */\nmodule.exports = {\n  'ember/template-no-let-reference': 'error',\n};\n"
  },
  {
    "path": "lib/recommended-rules.js",
    "content": "/*\n * IMPORTANT!\n * This file has been automatically generated.\n * In order to update its content based on rules'\n * definitions, execute \"npm run update\"\n */\nmodule.exports = {\n  \"ember/avoid-leaking-state-in-ember-objects\": \"error\",\n  \"ember/avoid-using-needs-in-controllers\": \"error\",\n  \"ember/classic-decorator-hooks\": \"error\",\n  \"ember/classic-decorator-no-classic-methods\": \"error\",\n  \"ember/closure-actions\": \"error\",\n  \"ember/jquery-ember-run\": \"error\",\n  \"ember/new-module-imports\": \"error\",\n  \"ember/no-actions-hash\": \"error\",\n  \"ember/no-arrow-function-computed-properties\": \"error\",\n  \"ember/no-assignment-of-untracked-properties-used-in-tracking-contexts\": \"error\",\n  \"ember/no-at-ember-render-modifiers\": \"error\",\n  \"ember/no-attrs-in-components\": \"error\",\n  \"ember/no-attrs-snapshot\": \"error\",\n  \"ember/no-capital-letters-in-routes\": \"error\",\n  \"ember/no-classic-classes\": \"error\",\n  \"ember/no-classic-components\": \"error\",\n  \"ember/no-component-lifecycle-hooks\": \"error\",\n  \"ember/no-computed-properties-in-native-classes\": \"error\",\n  \"ember/no-controller-access-in-routes\": \"error\",\n  \"ember/no-deeply-nested-dependent-keys-with-each\": \"error\",\n  \"ember/no-deprecated-router-transition-methods\": \"error\",\n  \"ember/no-duplicate-dependent-keys\": \"error\",\n  \"ember/no-ember-super-in-es-classes\": \"error\",\n  \"ember/no-ember-testing-in-module-scope\": \"error\",\n  \"ember/no-empty-glimmer-component-classes\": \"error\",\n  \"ember/no-function-prototype-extensions\": \"error\",\n  \"ember/no-get-with-default\": \"error\",\n  \"ember/no-get\": \"error\",\n  \"ember/no-global-jquery\": \"error\",\n  \"ember/no-implicit-injections\": \"error\",\n  \"ember/no-incorrect-calls-with-inline-anonymous-functions\": \"error\",\n  \"ember/no-incorrect-computed-macros\": \"error\",\n  \"ember/no-invalid-debug-function-arguments\": \"error\",\n  \"ember/no-invalid-dependent-keys\": \"error\",\n  \"ember/no-invalid-test-waiters\": \"error\",\n  \"ember/no-jquery\": \"error\",\n  \"ember/no-legacy-test-waiters\": \"error\",\n  \"ember/no-mixins\": \"error\",\n  \"ember/no-new-mixins\": \"error\",\n  \"ember/no-noop-setup-on-error-in-before\": \"error\",\n  \"ember/no-observers\": \"error\",\n  \"ember/no-old-shims\": \"error\",\n  \"ember/no-on-calls-in-components\": \"error\",\n  \"ember/no-pause-test\": \"error\",\n  \"ember/no-private-routing-service\": \"error\",\n  \"ember/no-restricted-resolver-tests\": \"error\",\n  \"ember/no-runloop\": \"error\",\n  \"ember/no-settled-after-test-helper\": \"error\",\n  \"ember/no-shadow-route-definition\": \"error\",\n  \"ember/no-side-effects\": \"error\",\n  \"ember/no-string-prototype-extensions\": \"error\",\n  \"ember/no-test-and-then\": \"error\",\n  \"ember/no-test-import-export\": \"error\",\n  \"ember/no-test-module-for\": \"error\",\n  \"ember/no-test-support-import\": \"error\",\n  \"ember/no-test-this-render\": \"error\",\n  \"ember/no-tracked-properties-from-args\": \"error\",\n  \"ember/no-try-invoke\": \"error\",\n  \"ember/no-unnecessary-route-path-option\": \"error\",\n  \"ember/no-volatile-computed-properties\": \"error\",\n  \"ember/prefer-ember-test-helpers\": \"error\",\n  \"ember/require-computed-macros\": \"error\",\n  \"ember/require-computed-property-dependencies\": \"error\",\n  \"ember/require-return-from-computed\": \"error\",\n  \"ember/require-super-in-lifecycle-hooks\": \"error\",\n  \"ember/require-tagless-components\": \"error\",\n  \"ember/require-valid-css-selector-in-test-helpers\": \"error\",\n  \"ember/routes-segments-snake-case\": \"error\",\n  \"ember/use-brace-expansion\": \"error\",\n  \"ember/use-ember-data-rfc-395-imports\": \"error\"\n};\n"
  },
  {
    "path": "lib/recommended.mjs",
    "content": "import emberPlugin from './index.js';\nimport baseRules from './recommended-rules.js';\nimport gjsRules from './recommended-rules-gjs.js';\nimport gtsRules from './recommended-rules-gts.js';\nimport emberParser from 'ember-eslint-parser';\nimport emberHbsParser from 'ember-eslint-parser/hbs';\n\nexport const plugin = emberPlugin;\nexport const parser = emberParser;\nexport const hbsParser = emberHbsParser;\n\nexport const base = {\n  name: 'ember:base',\n  files: ['**/*.{js,ts}'],\n  plugins: { ember: emberPlugin },\n  rules: {\n    ...baseRules,\n  },\n};\n\nexport const gjs = {\n  name: 'ember:gjs',\n  plugins: { ember: emberPlugin },\n  files: ['**/*.gjs'],\n  languageOptions: {\n    parser: emberParser,\n    parserOptions: {\n      ecmaFeatures: { modules: true },\n      ecmaVersion: 'latest',\n      // babel config options should be supplied in the consuming project\n    },\n  },\n  processor: 'ember/noop',\n  rules: {\n    ...base.rules,\n    ...gjsRules,\n  },\n};\n\nexport const gts = {\n  name: 'ember:gts',\n  plugins: { ember: emberPlugin },\n  files: ['**/*.gts'],\n  languageOptions: {\n    parser: emberParser,\n    parserOptions: {\n      extraFileExtensions: ['.gts'],\n    },\n    // parser options should be supplied in the consuming project\n  },\n  processor: 'ember/noop',\n  rules: {\n    ...base.rules,\n    ...gtsRules,\n  },\n};\n\nexport default {\n  // Helpful utility exports\n  plugin,\n  parser,\n  hbsParser,\n  // Recommended config sets\n  configs: {\n    base,\n    gjs,\n    gts,\n  },\n};\n"
  },
  {
    "path": "lib/rules/alias-model-in-controller.js",
    "content": "'use strict';\n\nconst utils = require('../utils/utils');\nconst ember = require('../utils/ember');\n\n//------------------------------------------------------------------------------\n// Controllers - Alias your model\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce aliasing model in controllers',\n      category: 'Controllers',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/alias-model-in-controller.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    const message = 'Alias your model';\n\n    const report = function (node) {\n      context.report({ node, message });\n    };\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (!ember.isEmberController(context, node)) {\n          return;\n        }\n\n        const properties = ember.getModuleProperties(node, scopeManager);\n        let aliasPresent = false;\n\n        for (const property of properties) {\n          const parsedCallee = utils.parseCallee(property.value);\n          const parsedArgs = utils.parseArgs(property.value);\n\n          if (\n            parsedCallee.length > 0 &&\n            ['alias', 'readOnly', 'reads'].includes(parsedCallee.pop()) &&\n            (parsedArgs[0] === 'model' || String(parsedArgs[0]).startsWith('model.'))\n          ) {\n            aliasPresent = true;\n          }\n        }\n\n        if (!aliasPresent) {\n          report(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/avoid-leaking-state-in-ember-objects.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\n\nconst DEFAULT_IGNORED_PROPERTIES = [\n  'classNames',\n  'classNameBindings',\n  'actions',\n  'concatenatedProperties',\n  'mergedProperties',\n  'positionalParams',\n  'attributeBindings',\n  'queryParams',\n  'attrs',\n];\n\nfunction isAllowedTernary(value) {\n  return (\n    types.isConditionalExpression(value) &&\n    isAllowed(value.consequent) &&\n    isAllowed(value.alternate)\n  );\n}\n\nfunction isAllowedLogicalExpression(value) {\n  return types.isLogicalExpression(value) && isAllowed(value.left) && isAllowed(value.right);\n}\n\nfunction isAllowed(value) {\n  // Unwrap TypeScript type assertions: `x as Type` or `x satisfies Type`\n  if (value && (value.type === 'TSAsExpression' || value.type === 'TSSatisfiesExpression')) {\n    return isAllowed(value.expression);\n  }\n\n  return (\n    ember.isFunctionExpression(value) ||\n    types.isLiteral(value) ||\n    types.isIdentifier(value) ||\n    types.isCallExpression(value) ||\n    types.isBinaryExpression(value) ||\n    types.isTemplateLiteral(value) ||\n    types.isTaggedTemplateExpression(value) ||\n    types.isMemberExpression(value) ||\n    types.isUnaryExpression(value) ||\n    isAllowedTernary(value) ||\n    isAllowedLogicalExpression(value)\n  );\n}\n\n//------------------------------------------------------------------------------\n// Ember object rule - Avoid leaking state\n// (Don't use arrays or objects as default props)\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow state leakage',\n      category: 'Ember Object',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/avoid-leaking-state-in-ember-objects.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'array',\n        uniqueItems: true,\n        minItems: 1,\n        items: { type: 'string' },\n      },\n    ],\n  },\n\n  create(context) {\n    const ignoredProperties = context.options[0]\n      ? [...DEFAULT_IGNORED_PROPERTIES, ...context.options[0]]\n      : DEFAULT_IGNORED_PROPERTIES;\n\n    const report = function (node) {\n      const message =\n        'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties';\n      context.report({ node, message });\n    };\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (\n          !(\n            ember.isExtendObject(node) ||\n            ember.isReopenObject(node) ||\n            ember.isEmberMixin(context, node)\n          )\n        ) {\n          return;\n        }\n\n        const properties = ember.getModuleProperties(node, scopeManager);\n\n        for (const property of properties.filter(\n          (property) => property.key && !ignoredProperties.includes(property.key.name)\n        )) {\n          if (!isAllowed(property.value)) {\n            report(property);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/avoid-using-needs-in-controllers.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\n\n//------------------------------------------------------------------------------\n// Ember object rule - Avoid using needs in controllers\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow using `needs` in controllers',\n      category: 'Controllers',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/avoid-using-needs-in-controllers.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    const report = function (node) {\n      const message =\n        '`needs` API has been deprecated, `Ember.inject.controller` should be used instead';\n      context.report({ node, message });\n    };\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        const isReopenNode = ember.isReopenObject(node) || ember.isReopenClassObject(node);\n\n        if (!ember.isEmberController(context, node) && !isReopenNode) {\n          return;\n        }\n\n        const properties = ember.getModuleProperties(node, scopeManager);\n\n        for (const property of properties) {\n          if (\n            property.type === 'Property' &&\n            property.key.type === 'Identifier' &&\n            property.key.name === 'needs'\n          ) {\n            report(property);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/classic-decorator-hooks.js",
    "content": "const ERROR_MESSAGE_INIT_IN_NON_CLASSIC =\n  'You cannot use the init() lifecycle hook in non-classic classes, it is a classic lifecycle hook. Convert to using the constructor instead, or add the @classic decorator to the class to mark it as classic.';\nconst ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC =\n  'You cannot use the destroy() lifecycle hook in non-classic classes, it is a classic lifecycle hook. Convert to using the willDestroy() lifecycle hook instead, or add the @classic decorator to the class to mark it as classic.';\nconst ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC =\n  'You cannot use the constructor in a classic class. If the class can be converted, you can convert it to remove all classic APIs and remove the @classic decorator, then switch to the constructor.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE_INIT_IN_NON_CLASSIC,\n  ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC,\n  ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC,\n\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'enforce using correct hooks for both classic and non-classic classes',\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/classic-decorator-hooks.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    let inClassicClass = false;\n    let inNonClassicClass = false;\n\n    return {\n      ClassDeclaration(node) {\n        if (node.superClass) {\n          if (node.decorators && node.decorators.some((d) => d.expression.name === 'classic')) {\n            inClassicClass = true;\n          } else {\n            inNonClassicClass = true;\n          }\n        }\n      },\n\n      'ClassDeclaration:exit'() {\n        inClassicClass = inNonClassicClass = false;\n      },\n\n      MethodDefinition(node) {\n        if (!inClassicClass && !inNonClassicClass) {\n          return;\n        }\n\n        if (inClassicClass && node.key.name === 'constructor') {\n          context.report({\n            node,\n            message: ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC,\n          });\n        }\n\n        if (inNonClassicClass && node.key.name === 'init') {\n          context.report({\n            node,\n            message: ERROR_MESSAGE_INIT_IN_NON_CLASSIC,\n          });\n        }\n\n        if (inNonClassicClass && node.key.name === 'destroy') {\n          context.report({\n            node,\n            message: ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/classic-decorator-no-classic-methods.js",
    "content": "const disallowedMethods = new Set([\n  'get',\n  'set',\n  'getProperties',\n  'setProperties',\n  'getWithDefault',\n  'incrementProperty',\n  'decrementProperty',\n  'toggleProperty',\n  'addObserver',\n  'removeObserver',\n  'notifyPropertyChange',\n  'cacheFor',\n  'proto',\n]);\n\nfunction disallowedMethodErrorMessage(name) {\n  return `The this.${name}() method is a classic ember object method, and can't be used in octane classes. You can refactor this usage to use a utility version instead (e.g. get(this, 'foo')), or to use native/modern syntax instead. Alternatively, you can add the @classic decorator to this class to continue using classic APIs.`;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  disallowedMethodErrorMessage,\n\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        \"disallow usage of classic APIs such as `get`/`set` in classes that aren't explicitly decorated with `@classic`\",\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/classic-decorator-no-classic-methods.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    let inClassExtends = false;\n\n    return {\n      ClassDeclaration(node) {\n        if (\n          node.superClass &&\n          !(node.decorators && node.decorators.some((d) => d.expression.name === 'classic'))\n        ) {\n          inClassExtends = true;\n        }\n      },\n\n      'ClassDeclaration:exit'() {\n        inClassExtends = false;\n      },\n\n      MemberExpression(node) {\n        if (!inClassExtends) {\n          return;\n        }\n        if (node.object.type !== 'ThisExpression' || node.property.type === 'PrivateIdentifier') {\n          return;\n        }\n\n        if (disallowedMethods.has(node.property.name)) {\n          context.report({\n            node,\n            message: disallowedMethodErrorMessage(node.property.name),\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/closure-actions.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\n\n//------------------------------------------------------------------------------\n// Components - Closure actions\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = 'Use closure actions';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of closure actions',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/closure-actions.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      MemberExpression(node) {\n        const isSendAction =\n          types.isThisExpression(node.object) &&\n          types.isIdentifier(node.property) &&\n          node.property.name === 'sendAction';\n\n        if (isSendAction) {\n          report(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/computed-property-getters.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// General rule - Prevent using a getter inside computed properties.\n//------------------------------------------------------------------------------\n\nconst ALWAYS_GETTER_MESSAGE = 'Always use a getter inside computed properties.';\nconst PREVENT_GETTER_MESSAGE = 'Do not use a getter inside computed properties.';\nconst ALWAYS_WITH_SETTER_MESSAGE =\n  'Always define a getter inside computed properties when using a setter.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce the consistent use of getters in computed properties',\n      category: 'Computed Properties',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/computed-property-getters.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        enum: ['always-with-setter', 'always', 'never'],\n      },\n    ],\n  },\n\n  ALWAYS_GETTER_MESSAGE,\n  PREVENT_GETTER_MESSAGE,\n  ALWAYS_WITH_SETTER_MESSAGE,\n\n  create(context) {\n    const requireGetters = context.options[0] || 'always-with-setter';\n\n    const report = function (node, message) {\n      context.report({ node, message });\n    };\n\n    const requireGetterOnlyWithASetterInComputedProperty = function (node) {\n      const objectExpressions = node.arguments.filter((arg) => types.isObjectExpression(arg));\n      if (objectExpressions.length > 0) {\n        const { properties } = objectExpressions[0];\n        const getters = properties.filter(\n          (prop) => prop.key && prop.key.name && prop.key.name === 'get'\n        );\n        const setters = properties.filter(\n          (prop) => prop.key && prop.key.name && prop.key.name === 'set'\n        );\n        if (\n          (setters.length > 0 && getters.length === 0) ||\n          (getters.length > 0 && setters.length === 0)\n        ) {\n          report(node, ALWAYS_WITH_SETTER_MESSAGE);\n        }\n      }\n    };\n\n    const preventGetterInComputedProperty = function (node) {\n      const objectExpressions = node.arguments.filter((arg) => types.isObjectExpression(arg));\n      if (objectExpressions.length > 0) {\n        const { properties } = objectExpressions[0];\n        const getters = properties.filter(\n          (prop) => prop.key && prop.key.name && prop.key.name === 'get'\n        );\n        const setters = properties.filter(\n          (prop) => prop.key && prop.key.name && prop.key.name === 'set'\n        );\n        if (getters.length > 0 || setters.length > 0) {\n          report(node, PREVENT_GETTER_MESSAGE);\n        }\n      }\n    };\n\n    const requireGetterInComputedProperty = function (node) {\n      const objectExpressions = node.arguments.filter((arg) => types.isObjectExpression(arg));\n      const functionExpressions = node.arguments.filter((arg) => types.isFunctionExpression(arg));\n\n      if (objectExpressions.length > 0) {\n        const { properties } = objectExpressions[0];\n\n        const getters = properties.filter(\n          (prop) => prop.key && prop.key.name && prop.key.name === 'get'\n        );\n        if (getters.length === 0) {\n          report(node, ALWAYS_GETTER_MESSAGE);\n        }\n      } else if (functionExpressions.length > 0) {\n        report(node, ALWAYS_GETTER_MESSAGE);\n      }\n    };\n\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          ember.isComputedProp(node, importedEmberName, importedComputedName) &&\n          node.arguments.length > 0\n        ) {\n          if (requireGetters === 'always-with-setter') {\n            requireGetterOnlyWithASetterInComputedProperty(node);\n          }\n          if (requireGetters === 'always') {\n            requireGetterInComputedProperty(node);\n          }\n          if (requireGetters === 'never') {\n            preventGetterInComputedProperty(node);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/jquery-ember-run.js",
    "content": "'use strict';\n\nconst utils = require('../utils/utils');\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\nconst { ReferenceTracker } = require('eslint-utils');\nconst { globalMap, esmMap } = require('../utils/jquery');\n\n//------------------------------------------------------------------------------\n// General rule - Don’t use jQuery without Ember Run Loop\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = \"Don't use jQuery without Ember Run Loop\";\n\n// https://api.emberjs.com/ember/3.24/classes/@ember%2Frunloop\nconst EMBER_RUNLOOP_FUNCTIONS = [\n  'begin',\n  'bind',\n  'cancel',\n  'debounce',\n  'end',\n  'join',\n  'later',\n  'next',\n  'once',\n  'run',\n  'schedule',\n  'scheduleOnce',\n  'throttle',\n];\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of jQuery without an Ember run loop',\n      category: 'jQuery',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/jquery-ember-run.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    let importedEmberName;\n    const importedRunloopFunctions = [];\n\n    // Check for imported call to: bind()\n    function isBindCall(expression) {\n      return (\n        types.isCallExpression(expression) &&\n        expression.callee.type === 'Identifier' &&\n        importedRunloopFunctions.includes(expression.callee.name)\n      );\n    }\n\n    // Check for old-style: Ember.run.bind()\n    function isEmberBindCall(expression) {\n      return (\n        types.isCallExpression(expression) &&\n        expression.callee.type === 'MemberExpression' &&\n        expression.callee.property.type === 'Identifier' &&\n        EMBER_RUNLOOP_FUNCTIONS.includes(expression.callee.property.name) &&\n        expression.callee.object.type === 'MemberExpression' &&\n        expression.callee.object.property.type === 'Identifier' &&\n        expression.callee.object.property.name === 'run' &&\n        expression.callee.object.object.type === 'Identifier' &&\n        expression.callee.object.object.name === importedEmberName\n      );\n    }\n\n    function checkJqueryCall(node) {\n      if (\n        // Check to see if this jquery call looks like: $(...).on(() => { ... }));\n        node.parent.type === 'MemberExpression' &&\n        node.parent.object === node &&\n        node.parent.property.type === 'Identifier' &&\n        node.parent.property.name === 'on' &&\n        node.parent.parent.type === 'CallExpression'\n      ) {\n        const onCall = node.parent.parent;\n        const fnNodes = utils.findNodes(onCall.arguments, 'ArrowFunctionExpression');\n        for (const fnNode of fnNodes) {\n          const fnBody = fnNode.body.body;\n          const fnExpressions = utils.findNodes(fnBody, 'ExpressionStatement');\n          for (const fnExpression of fnExpressions) {\n            const expression = fnExpression.expression;\n            if (!isBindCall(expression) && !isEmberBindCall(expression)) {\n              if (types.isCallExpression(expression)) {\n                report(expression.callee);\n              } else {\n                report(expression);\n              }\n            }\n          }\n        }\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/runloop') {\n          importedRunloopFunctions.push(\n            ...EMBER_RUNLOOP_FUNCTIONS.map((fn) =>\n              getImportIdentifier(node, '@ember/runloop', fn)\n            ).filter((fn) => fn !== undefined)\n          );\n        }\n      },\n\n      'Program:exit'(node) {\n        const sourceCode = context.sourceCode;\n        const scope = sourceCode.getScope(node);\n\n        const tracker = new ReferenceTracker(scope);\n\n        /**\n         * Global references\n         *\n         * eg; $(body) and $.post()\n         */\n        for (const { node } of tracker.iterateGlobalReferences(globalMap)) {\n          checkJqueryCall(node);\n        }\n\n        /**\n         * ESM references\n         *   import $ from 'jquery'\n         *   import { $ as jq } from 'ember'\n         *\n         * eg;\n         *   $(body) and jq.post()\n         */\n        for (const { node } of tracker.iterateEsmReferences(esmMap)) {\n          checkJqueryCall(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/named-functions-in-promises.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\n\n//------------------------------------------------------------------------------\n// General rule - Use named functions defined on objects to handle promises\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of named functions in promises',\n      category: 'Miscellaneous',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/named-functions-in-promises.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowSimpleArrowFunction: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Enabling allows arrow function expressions that do not have block bodies. These simple arrow functions must also only contain a single function call. For example: `.then(user => this._reloadUser(user))`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const allowSimpleArrowFunction = options.allowSimpleArrowFunction || false;\n\n    const message = 'Use named functions defined on objects to handle promises';\n\n    const report = function (node) {\n      context.report({ node, message });\n    };\n\n    return {\n      CallExpression(node) {\n        const firstArg = node.arguments[0];\n\n        if (hasPromiseExpression(node)) {\n          if (\n            allowSimpleArrowFunction &&\n            types.isConciseArrowFunctionWithCallExpression(firstArg)\n          ) {\n            return;\n          }\n          if (types.isFunctionExpression(firstArg) || types.isArrowFunctionExpression(firstArg)) {\n            report(node);\n          }\n        }\n      },\n    };\n  },\n};\n\nfunction hasPromiseExpression(node) {\n  const callee = node.callee;\n  const promisesMethods = new Set(['then', 'catch', 'finally']);\n\n  return (\n    types.isCallExpression(callee.object) &&\n    types.isIdentifier(callee.property) &&\n    promisesMethods.has(callee.property.name)\n  );\n}\n"
  },
  {
    "path": "lib/rules/new-module-imports.js",
    "content": "'use strict';\n\nconst MAPPING = require('ember-rfc176-data');\nconst { buildMessage, getFullNames, isDestructuring } = require('../utils/new-module');\nconst { getSourceModuleNameForIdentifier } = require('../utils/import');\n\nconst GLOBALS = MAPPING.reduce((memo, exportDefinition) => {\n  if (exportDefinition.deprecated) {\n    return memo;\n  }\n\n  if (exportDefinition.global in memo) {\n    return memo;\n  }\n\n  memo[exportDefinition.global] = exportDefinition; // eslint-disable-line no-param-reassign\n\n  return memo;\n}, Object.create(null));\n\n//------------------------------------------------------------------------------\n// General rule - Use \"New Module Imports\" from Ember RFC #176\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'enforce using \"New Module Imports\" from Ember RFC #176',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/new-module-imports.md',\n    },\n    schema: [],\n  },\n\n  create(context) {\n    function reportNestedProperties(properties, parent) {\n      for (const item of properties) {\n        const match = GLOBALS[`Ember.${parent}.${item.key.name}`];\n\n        if (match) {\n          const message = buildMessage({\n            node: item,\n            key: item.key.name,\n            match,\n            parent,\n            type: item.type,\n          });\n\n          context.report({ node: item, message });\n        }\n      }\n    }\n\n    return {\n      VariableDeclarator(node) {\n        if (\n          !isDestructuring(node) ||\n          getSourceModuleNameForIdentifier(context, node.init) !== 'ember'\n        ) {\n          return;\n        }\n\n        const properties = node.id.properties;\n        // Iterate through the destructured properties and report them\n        for (const item of properties) {\n          // Locate nested destructuring\n          if (item.value.properties) {\n            const parent = item.key.name;\n            const props = item.value.properties;\n            reportNestedProperties(props, parent);\n          } else {\n            const key = item.key.name;\n            const match = GLOBALS[`Ember.${key}`];\n\n            if (match) {\n              const message = buildMessage({\n                node: item,\n                customKey: key === item.value.name ? null : item.value.name,\n                key,\n                match,\n                type: item.type,\n              });\n\n              context.report({ node: item, message });\n            }\n          }\n        }\n      },\n\n      'MemberExpression > Identifier[name=Ember]'(node) {\n        // filter out \"foo.Ember\"\n        if (node.parent.object !== node) {\n          return;\n        }\n\n        // build an array of full expression names\n        // e.g. [Ember.computed, Ember.computed.or]\n        let fullName = 'Ember';\n        const fullNames = getFullNames(fullName, node);\n\n        // find a matching expression starting at the end\n        for (const element of fullNames) {\n          fullName = element;\n\n          const key = fullName.replace(/^Ember\\./, '');\n          const match = GLOBALS[fullName];\n\n          // if a given global path does not exist in `mappings.json` there is no\n          // JS module import for it, so do not report the error\n          if (match) {\n            const message = buildMessage({ node, fullName, key, match });\n            context.report({ node, message });\n            break;\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-actions-hash.js",
    "content": "const ember = require('../utils/ember');\nconst utils = require('../utils/utils');\n\nconst ERROR_MESSAGE = 'Use the @action decorator instead of declaring an actions hash';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow the actions hash in components, controllers, and routes',\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-actions-hash.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create: (context) => {\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    function reportActionsProp(properties) {\n      const actionsProp = properties.find((property) => ember.isActionsProp(property));\n      if (actionsProp) {\n        context.report({ node: actionsProp, message: ERROR_MESSAGE });\n      }\n    }\n\n    return {\n      ClassDeclaration(node) {\n        if (inClassWhichCanContainActions(context, node)) {\n          reportActionsProp([\n            ...utils.findNodes(node.body.body, 'ClassProperty'), // ESLint v7\n            ...utils.findNodes(node.body.body, 'PropertyDefinition'), // ESLint v8\n          ]);\n        }\n      },\n      CallExpression(node) {\n        if (inClassWhichCanContainActions(context, node)) {\n          reportActionsProp(ember.getModuleProperties(node, scopeManager));\n        }\n      },\n    };\n  },\n};\n\nfunction inClassWhichCanContainActions(context, node) {\n  return (\n    ember.isEmberComponent(context, node) ||\n    ember.isEmberController(context, node) ||\n    ember.isEmberRoute(context, node)\n  );\n}\n"
  },
  {
    "path": "lib/rules/no-array-prototype-extensions.js",
    "content": "'use strict';\n\nconst { getName, getNodeOrNodeFromVariable } = require('../utils/utils');\nconst { isClassPropertyOrPropertyDefinition } = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\nconst { insertImportDeclaration } = require('../utils/fixer');\nconst Stack = require('../utils/stack');\n\nconst ERROR_MESSAGE = \"Don't use Ember's array prototype extensions\";\nconst TOKEN_TYPES = {\n  PUNCTUATOR: 'Punctuator',\n};\n\nconst EXTENSION_METHODS = new Set([\n  /**\n   * https://api.emberjs.com/ember/release/classes/EmberArray\n   * EmberArray methods excluding native functions like reduce, filter etc.\n   * */\n  'any',\n  'compact',\n  'filterBy',\n  'findBy',\n  'getEach',\n  'invoke',\n  'isAny',\n  'isEvery',\n  'mapBy',\n  'objectAt',\n  'objectsAt',\n  'reject',\n  'rejectBy',\n  'setEach',\n  'sortBy',\n  'toArray',\n  'uniq',\n  'uniqBy',\n  'without',\n  /**\n   * https://api.emberjs.com/ember/release/classes/MutableArray\n   * MutableArray methods excluding `replace`. `replace` is handled differently as it's also part of String.prototype.\n   * */\n  'addObject',\n  'addObjects',\n  'clear',\n  'insertAt',\n  'popObject',\n  'pushObject',\n  'pushObjects',\n  'removeAt',\n  'removeObject',\n  'removeObjects',\n  'reverseObjects',\n  'setObjects',\n  'shiftObject',\n  'unshiftObject',\n  'unshiftObjects',\n]);\n\nconst REPLACE_METHOD = 'replace';\n\n/**\n * https://api.emberjs.com/ember/release/classes/EmberArray\n * EmberArray properties excluding native props: [], length.\n * */\nconst EXTENSION_PROPERTIES = new Set(['lastObject', 'firstObject']);\n\n/**\n * Ignore these function calls.\n */\nconst KNOWN_NON_ARRAY_FUNCTION_CALLS = new Set([\n  // Promise.reject()\n  'window.Promise.reject()',\n  'Promise.reject()',\n\n  // RSVP.reject\n  'RSVP.reject()',\n  'RSVP.Promise.reject()',\n  'Ember.RSVP.reject()',\n  'Ember.RSVP.Promise.reject()',\n\n  // Promise.any()\n  'window.Promise.any()',\n  'Promise.any()',\n\n  // *storage.clear()\n  'window.localStorage.clear()',\n  'window.sessionStorage.clear()',\n  'localStorage.clear()',\n  'sessionStorage.clear()',\n]);\n\n/**\n * Ignore these function calls using RegExps.\n */\nconst KNOWN_NON_ARRAY_FUNCTION_CALLS_REGEXP = new Set([\n  // ember-cli-mirage: server.schema.X.findBy(), schema.X.findBy(), server.X.findBy(), server.db.X.findBy()\n  /(^|\\.)(server|schema)\\b.*\\.findBy\\(\\)/,\n]);\n\n/**\n * Ignore objects of these names.\n */\nconst KNOWN_NON_ARRAY_OBJECTS = new Set([\n  // lodash\n  '_',\n  'lodash',\n\n  // jquery\n  '$()',\n  'jQuery()',\n  'jquery()',\n]);\n\n/**\n * Ignore variables initialized to instances of these classes.\n */\nconst KNOWN_NON_ARRAY_CLASSES = new Set([\n  // These Set/Map data structure classes have an overlapping clear() method.\n  'Set',\n  'Map',\n  'WeakSet',\n  'WeakMap',\n  // https://github.com/tracked-tools/tracked-built-ins\n  'TrackedSet',\n  'TrackedMap',\n  'TrackedWeakSet',\n  'TrackedWeakMap',\n]);\n\n/**\n * Ignore certain function calls made on variables containing certain words as they are likely to be instances of non-array classes.\n * Words stored in lowercase.\n */\nconst FN_NAMES_TO_KNOWN_NON_ARRAY_WORDS = new Map([\n  // These Promise-related objects have an overlapping reject() method.\n  ['reject', new Set(['deferred', 'promise'])],\n  // These Set/Map data structure classes have an overlapping clear() method.\n  ['clear', new Set(['set', 'map'])],\n]);\n\n/**\n * Return the (lowercase) list of words in a variable name of various casings (camelCase, PascalCase, snake_case, UPPER_CASE).\n * @param {string} name - variable name\n * @returns {string[]} list of (lowercase) words in the variable name\n */\nfunction variableNameToWords(name) {\n  if (name.includes('_')) {\n    // snake_case, UPPER_CASE.\n    return name.toLowerCase().trim().split('_');\n  }\n\n  if (name === name.toUpperCase()) {\n    // Single word.\n    return [name.toLowerCase()];\n  }\n\n  // camelCase, PascalCase.\n  return name\n    .replaceAll(/([A-Z])/g, ' $1')\n    .toLowerCase()\n    .trim()\n    .split(' ');\n}\n\n/**\n * Returns the fixing object if it can be fixable otherwise returns an empty array.\n *\n * @param {Object} callExpressionNode The call expression AST node.\n * @param {Object} fixer The ESLint fixer object which will be used to apply fixes.\n * @param {Object} context The ESlint context object which contains some helper utils\n * @param {Object} [options] An object that contains additional information\n * @param {String} [options.importedGetName] The name of the imported get specifier from @ember/object package\n * @param {String} [options.importedSetName] The name of the imported set specifier from @ember/object package\n * @param {String} [options.importedCompareName] The name of the imported compare specifier from @ember/utils package\n * @returns {Object|[]}\n */\n// eslint-disable-next-line complexity\nfunction applyFix(callExpressionNode, fixer, context, options = {}) {\n  const calleeProp = callExpressionNode.callee.property;\n  const propertyName = calleeProp.name;\n  const calleeObj = callExpressionNode.callee.object;\n  const callArgs = callExpressionNode.arguments;\n  const sourceCode = context.sourceCode;\n\n  // Get the open parenthesis immediately after the callee name\n  const openParenToken = sourceCode.getTokenAfter(calleeProp, {\n    filter(token) {\n      return token.type === TOKEN_TYPES.PUNCTUATOR && token.value === '(';\n    },\n  });\n  // Get the close parenthesis from the end of the callExpressionNode\n  const closeParenToken = sourceCode.getLastToken(callExpressionNode, {\n    filter(token) {\n      return token.type === TOKEN_TYPES.PUNCTUATOR && token.value === ')';\n    },\n  });\n\n  switch (propertyName) {\n    case 'any': {\n      return fixer.replaceText(calleeProp, 'some');\n    }\n    case 'compact': {\n      const fixes = [];\n\n      if (openParenToken && closeParenToken && callArgs.length === 0) {\n        fixes.push(\n          fixer.replaceText(calleeProp, 'filter'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            '(item => item !== undefined && item !== null)'\n          )\n        );\n      }\n      return fixes;\n    }\n    case 'filterBy':\n    case 'findBy':\n    case 'isAny':\n    case 'isEvery': {\n      const fixes = [];\n\n      if (callArgs.length > 0 && callArgs.length < 3) {\n        const hasSecondArg = callArgs.length > 1;\n        const firstArg = callArgs[0];\n        const secondArg = callArgs[1];\n\n        let replacementMethod;\n\n        switch (propertyName) {\n          case 'findBy': {\n            replacementMethod = 'find';\n            break;\n          }\n          case 'isAny': {\n            replacementMethod = 'some';\n            break;\n          }\n          case 'isEvery': {\n            replacementMethod = 'every';\n            break;\n          }\n          default: {\n            replacementMethod = 'filter';\n          }\n        }\n\n        // default to `get` if the `get` hasn't already been imported.\n        const importedGetName = options.importedGetName ?? 'get';\n\n        fixes.push(\n          fixer.replaceText(calleeProp, replacementMethod),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          // If the findBy contains two arguments, the property (first argument) value will be compared against the second argument\n          // If the filterBy contains only one argument, the property's truthy value is used to filter\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            `(item => ${importedGetName}(item, ${sourceCode.getText(firstArg)})${\n              hasSecondArg ? ` === ${sourceCode.getText(secondArg)}` : ''\n            })`\n          )\n        );\n\n        // Add `get` import statement only if it is not imported already\n        if (!options.importedGetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));\n        }\n      }\n\n      return fixes;\n    }\n    case 'invoke': {\n      const fixes = [];\n\n      if (callArgs.length > 0) {\n        const argText = sourceCode.getText(callArgs[0]);\n        const restOfArgs = callArgs\n          .slice(1)\n          .map((arg) => sourceCode.getText(arg))\n          .join(', ');\n\n        // default to `get` if the `get` hasn't already been imported.\n        const importedGetName = options.importedGetName ?? 'get';\n\n        fixes.push(\n          fixer.replaceText(calleeProp, 'map'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            `(item => ${importedGetName}(item, ${argText})?.(${restOfArgs}))`\n          )\n        );\n\n        // Add `get` import statement only if it is not imported already\n        if (!options.importedGetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));\n        }\n      }\n      return fixes;\n    }\n    case 'mapBy':\n    case 'getEach': {\n      if (callArgs.length === 1) {\n        const argText = sourceCode.getText(callArgs[0]);\n        const fixes = [];\n\n        // default to `get` if the `get` hasn't already been imported.\n        const importedGetName = options.importedGetName ?? 'get';\n\n        fixes.push(\n          fixer.replaceText(calleeProp, 'map'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            `(item => ${importedGetName}(item, ${argText}))`\n          )\n        );\n\n        // Add `get` import statement only if it is not imported already\n        if (!options.importedGetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));\n        }\n\n        return fixes;\n      }\n      return [];\n    }\n    case 'objectAt': {\n      if (callArgs.length === 1) {\n        return [\n          fixer.insertTextBefore(\n            callExpressionNode,\n            `${sourceCode.getText(calleeObj)}${\n              callExpressionNode.callee.optional ? '?.' : ''\n            }[${sourceCode.getText(callArgs[0])}]`\n          ),\n          fixer.remove(callExpressionNode),\n        ];\n      }\n\n      return [];\n    }\n    case 'objectsAt': {\n      if (callArgs.length > 0) {\n        return [\n          fixer.insertTextBefore(\n            callExpressionNode,\n            `[${callArgs\n              .map((arg) => sourceCode.getText(arg))\n              .join(', ')}].map((ind) => ${sourceCode.getText(calleeObj)}[ind])`\n          ),\n          fixer.remove(callExpressionNode),\n        ];\n      }\n\n      return [];\n    }\n    case 'reject': {\n      if (callArgs.length > 0 && callArgs.length < 3) {\n        return [\n          fixer.replaceText(calleeProp, 'filter'),\n          // TODO: Switch to `Reflect.apply` once Ember v3 LTS support ends: https://emberjs.com/releases/lts/\n          fixer.replaceText(\n            callArgs[0],\n            `function(...args) { return !(${sourceCode.getText(callArgs[0])}).apply(this, args); }`\n          ),\n        ];\n      }\n      return [];\n    }\n    case 'rejectBy': {\n      const fixes = [];\n\n      if (callArgs.length > 0 && callArgs.length < 3) {\n        const hasSecondArg = callArgs.length > 1;\n        const firstArg = callArgs[0];\n        const secondArg = callArgs[1];\n\n        // default to `get` if the `get` hasn't already been imported.\n        const importedGetName = options.importedGetName ?? 'get';\n\n        fixes.push(\n          fixer.replaceText(calleeProp, 'filter'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          // If the findBy contains two arguments, the property (first argument) value will be compared against the second argument\n          // If the filterBy contains only one argument, the property's truthy value is used to filter\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            hasSecondArg\n              ? `(item => ${importedGetName}(item, ${sourceCode.getText(\n                  firstArg\n                )}) !== ${sourceCode.getText(secondArg)})`\n              : `(item => !${importedGetName}(item, ${sourceCode.getText(firstArg)}))`\n          )\n        );\n\n        // Add `get` import statement only if it is not imported already\n        if (!options.importedGetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));\n        }\n      }\n      return fixes;\n    }\n    case 'setEach': {\n      const fixes = [];\n\n      if (callArgs.length === 2) {\n        // default to `set` if the `set` hasn't already been imported.\n        const importedSetName = options.importedSetName ?? 'set';\n\n        fixes.push(\n          fixer.replaceText(calleeProp, 'forEach'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            `(item => ${importedSetName}(item, ${callArgs\n              .map((arg) => sourceCode.getText(arg))\n              .join(', ')}))`\n          )\n        );\n\n        // Add `set` import statement only if it is not imported already\n        if (!options.importedSetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedSetName));\n        }\n\n        return fixes;\n      }\n\n      return [];\n    }\n    case 'sortBy': {\n      const fixes = [];\n\n      if (callArgs.length > 0) {\n        // default to `compare` if the `compare` hasn't already been imported.\n        const importedCompareName = options.importedCompareName ?? 'compare';\n        // default to `compare` if the `compare` hasn't already been imported.\n        const importedGetName = options.importedGetName ?? 'get';\n\n        const argsText = callArgs.map((arg) => sourceCode.getText(arg)).join(', ');\n\n        let sortFn;\n        const comparisonFnInString = (key) =>\n          `${importedCompareName}(${importedGetName}(a, ${key}), ${importedGetName}(b, ${key}))`;\n        if (callArgs.length === 1 && callArgs[0].type !== 'SpreadElement') {\n          sortFn =\n            callArgs[0].type === 'Identifier' || callArgs[0].type === 'Literal'\n              ? `(a, b) => ${comparisonFnInString(argsText)}`\n              : `(a, b) => {\n              const key = ${argsText};\n              return ${comparisonFnInString('key')};\n            }`;\n        } else {\n          // Loop through keys if there are more than one argument\n          sortFn = `(a, b) => {\n            for (const key of [${argsText}]) {\n              const compareValue = ${comparisonFnInString('key')};\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          }`;\n        }\n\n        fixes.push(\n          // Duplicate array\n          fixer.replaceText(calleeObj, `[...${sourceCode.getText(calleeObj)}]`),\n          fixer.replaceText(calleeProp, 'sort'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange([openParenToken.range[0], closeParenToken.range[1]], `(${sortFn})`)\n        );\n\n        // Add `get` import statement only if it is not imported already\n        if (!options.importedGetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));\n        }\n\n        // Add `compare` import statement only if it is not imported already\n        if (!options.importedCompareName) {\n          fixes.push(\n            insertImportDeclaration(sourceCode, fixer, '@ember/utils', importedCompareName)\n          );\n        }\n      }\n\n      return fixes;\n    }\n    case 'toArray': {\n      if (callArgs.length === 0) {\n        return [\n          fixer.insertTextBefore(callExpressionNode, `[...${sourceCode.getText(calleeObj)}]`),\n          fixer.remove(callExpressionNode),\n        ];\n      }\n\n      return [];\n    }\n    case 'uniq': {\n      if (callArgs.length === 0) {\n        return [\n          fixer.insertTextBefore(\n            callExpressionNode,\n            `[...new Set(${sourceCode.getText(calleeObj)})]`\n          ),\n          fixer.remove(callExpressionNode),\n        ];\n      }\n\n      return [];\n    }\n    case 'uniqBy': {\n      const fixes = [];\n\n      if (callArgs.length === 1) {\n        const argInString = sourceCode.getText(callArgs[0]);\n        // default to `get` if the `get` hasn't already been imported.\n        const importedGetName = options.importedGetName ?? 'get';\n\n        let isFunctionArg;\n        let isLiteralArg;\n\n        switch (callArgs[0].type) {\n          case 'ArrowFunctionExpression':\n          case 'FunctionExpression': {\n            isFunctionArg = true;\n            break;\n          }\n          case 'Literal': {\n            isLiteralArg = true;\n            break;\n          }\n          default: {\n            break;\n          }\n        }\n\n        const uniqByFn = `([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }`;\n\n        let getterFnInText;\n        if (isLiteralArg) {\n          getterFnInText = `(item) => ${importedGetName}(item, ${argInString})`;\n        } else if (isFunctionArg) {\n          getterFnInText = argInString;\n        } else {\n          getterFnInText = `typeof ${argInString} === 'function' ? ${argInString} : (item) => ${importedGetName}(item, ${argInString})`;\n        }\n\n        const reducerInitialValueArr = ['[]', 'new Set()', getterFnInText];\n\n        fixes.push(\n          fixer.replaceText(calleeProp, 'reduce'),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            `(${uniqByFn}, [${reducerInitialValueArr.join(', ')}])[0]`\n          )\n        );\n\n        // Add `get` import statement only if it is not imported already\n        if (!options.importedGetName) {\n          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));\n        }\n\n        return fixes;\n      }\n\n      return [];\n    }\n    case 'without': {\n      if (callArgs.length === 1) {\n        const argText = sourceCode.getText(callArgs[0]);\n        const calleeObjText = sourceCode.getText(calleeObj);\n\n        return [\n          // As per https://api.emberjs.com/ember/release/classes/EmberArray/methods/mapBy?anchor=without\n          // when the passed value doesn't not exist in the array, it returns the original array\n          // Hence we first check for the existence of the passed value in the array before calling filter on array\n          // Used indexOf instead of includes since v3.x of ember is committed to supporting IE11\n          // as per the ember browser support policy (https://emberjs.com/browser-support/)\n          // TODO: Switch to includes once Ember v3 LTS support ends: https://emberjs.com/releases/lts/\n          fixer.replaceText(calleeProp, `indexOf(${argText}) > -1 ? ${calleeObjText}.filter`),\n          fixer.insertTextAfter(closeParenToken, ` : ${calleeObjText})`),\n          // Replacing the content starting from open parenthesis to close parenthesis\n          fixer.replaceTextRange(\n            [openParenToken.range[0], closeParenToken.range[1]],\n            `(item => item !== ${argText})`\n          ),\n          fixer.insertTextBefore(callExpressionNode, '('),\n        ];\n      }\n\n      return [];\n    }\n    default: {\n      return [];\n    }\n  }\n}\n\n/**\n * Check for a call on `this.store` which we can assume is the Ember Data store service.\n * We don't check for an initialization as the service could be implicitly injected: https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/no-implicit-injections.md\n */\nfunction isThisStoreCall(node) {\n  return (\n    node.type === 'CallExpression' &&\n    node.callee.type === 'MemberExpression' &&\n    node.callee.object.type === 'MemberExpression' &&\n    node.callee.object.object.type === 'ThisExpression' &&\n    node.callee.object.property.type === 'Identifier' &&\n    node.callee.object.property.name === 'store' &&\n    node.callee.property.type === 'Identifier' // Any function call on the store service.\n  );\n}\n\n//----------------------------------------------------------------------------------------------\n// General rule - Don't use Ember's array prototype extensions like .any(), .pushObject() or .firstObject\n//----------------------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: \"disallow usage of Ember's `Array` prototype extensions\",\n      category: 'Deprecations',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-array-prototype-extensions.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      main: ERROR_MESSAGE,\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n    let importedGetName;\n    let importedSetName;\n    let importedCompareName;\n    let importedEmberArrayName;\n\n    // Track some information about the current class we're inside.\n    const classStack = new Stack();\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object') {\n          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');\n          importedSetName = importedSetName || getImportIdentifier(node, '@ember/object', 'set');\n        }\n        if (node.source.value === '@ember/utils') {\n          importedCompareName =\n            importedCompareName || getImportIdentifier(node, '@ember/utils', 'compare');\n        }\n        if (node.source.value === '@ember/array') {\n          importedEmberArrayName =\n            importedEmberArrayName || getImportIdentifier(node, '@ember/array', 'A');\n        }\n      },\n      /**\n       * Cover cases when `EXTENSION_METHODS` is getting called.\n       * Example: something.filterBy();\n       * @param {Object} node\n       */\n      // eslint-disable-next-line complexity\n      CallExpression(node) {\n        // Skip case: filterBy();\n        if (node.callee.type !== 'MemberExpression') {\n          return;\n        }\n\n        // Skip case: this.filterBy(); super.filterBy();\n        if (['ThisExpression', 'Super'].includes(node.callee.object.type)) {\n          return;\n        }\n\n        if (node.callee.property.type !== 'Identifier') {\n          return;\n        }\n\n        const name = getName(node, true);\n        const nameParts = name.split('.');\n        if (\n          KNOWN_NON_ARRAY_FUNCTION_CALLS.has(name) ||\n          KNOWN_NON_ARRAY_OBJECTS.has(nameParts.at(-2))\n        ) {\n          // Ignore any known non-array objects/function calls to reduce false positives.\n          return;\n        }\n\n        for (const expression of KNOWN_NON_ARRAY_FUNCTION_CALLS_REGEXP) {\n          // Ignore any known non-array function calls matching a regular expression to reduce false positives.\n          if (expression.test(name)) {\n            return;\n          }\n        }\n\n        for (const functionName of FN_NAMES_TO_KNOWN_NON_ARRAY_WORDS.keys()) {\n          const words = FN_NAMES_TO_KNOWN_NON_ARRAY_WORDS.get(functionName);\n          if (\n            nameParts.at(-1) === `${functionName}()` &&\n            variableNameToWords(nameParts.at(-2)).some((word) => words.has(word))\n          ) {\n            // We found a function call on a variable whose name contains a word that indicates this variable is not an array.\n            return;\n          }\n        }\n\n        const nodeInitializedTo = getNodeOrNodeFromVariable(node.callee.object, scopeManager);\n        if (\n          nodeInitializedTo.type === 'NewExpression' &&\n          nodeInitializedTo.callee.type === 'Identifier' &&\n          KNOWN_NON_ARRAY_CLASSES.has(nodeInitializedTo.callee.name)\n        ) {\n          // Ignore when we can tell the variable was initialized to an instance of a non-array class.\n          // Example: const foo = new Set();\n          return;\n        }\n\n        if (\n          (nodeInitializedTo.type === 'AwaitExpression' &&\n            isThisStoreCall(nodeInitializedTo.argument)) ||\n          isThisStoreCall(nodeInitializedTo)\n        ) {\n          // Found call on the Ember Data this.store class.\n          return;\n        }\n\n        if (\n          node.callee.type === 'MemberExpression' &&\n          node.callee.object.type === 'MemberExpression' &&\n          node.callee.object.object.type === 'ThisExpression' &&\n          ['Identifier', 'PrivateIdentifier', 'PrivateName'].includes(\n            node.callee.object.property.type\n          ) &&\n          classStack.peek() &&\n          classStack.peek().classPropertiesToIgnore.has(\n            node.callee.object.property.type === 'Identifier'\n              ? node.callee.object.property.name\n              : `#${node.callee.object.property.name}` // Add # for private properties to avoid confusing public/private properties.\n          )\n        ) {\n          // Ignore when we can tell the class property was initialized to an instance of a non-array class.\n          // Example:\n          /*\n          class MyClass {\n            foo = new Set();\n            myFunc() { this.foo.clear() }\n          }\n          */\n          return;\n        }\n\n        // Direct usage of `@ember/array` is allowed.\n        if (\n          node.type === 'CallExpression' &&\n          importedEmberArrayName &&\n          nodeInitializedTo.type === 'CallExpression' &&\n          nodeInitializedTo.callee.type === 'Identifier' &&\n          importedEmberArrayName === nodeInitializedTo.callee.name\n        ) {\n          return;\n        }\n\n        if (EXTENSION_METHODS.has(node.callee.property.name)) {\n          context.report({\n            node,\n            messageId: 'main',\n            fix(fixer) {\n              return applyFix(node, fixer, context, {\n                importedCompareName,\n                importedGetName,\n                importedSetName,\n              });\n            },\n          });\n        }\n\n        // Example: someArray.replace(1, 2, [1, 2, 3]);\n        // We can differentiate String.prototype.replace and Array.prototype.replace by arguments length\n        // String.prototype.replace can only have 2 arguments, Array.prototype.replace needs to have exact 3 arguments\n        if (node.callee.property.name === REPLACE_METHOD && node.arguments.length === 3) {\n          context.report({ node, messageId: 'main' });\n        }\n      },\n\n      /**\n       * Cover cases when `EXTENSION_PROPERTIES` is accessed like:\n       * foo.firstObject;\n       * bar.lastObject.bar;\n       * @param {Object} node\n       */\n      MemberExpression(node) {\n        // Skip case when EXTENSION_PROPERTIES is accessed through callee.\n        // Example: something.firstObject()\n        if (node.parent.type === 'CallExpression') {\n          return;\n        }\n\n        if (node.property.type !== 'Identifier') {\n          return;\n        }\n        if (EXTENSION_PROPERTIES.has(node.property.name)) {\n          context.report({ node, messageId: 'main' });\n        }\n      },\n\n      /**\n       * Cover cases when `EXTENSION_PROPERTIES` is accessed through literals like:\n       * get(something, 'foo.firstObject');\n       * set(something, 'lastObject.bar', 'something');\n       * @param {Object} node\n       */\n      Literal(node) {\n        // Generate regexp for extension properties.\n        // new RegExp(`${[...EXTENSION_PROPERTIES].map(prop => `(\\.|^)${prop}(\\.|$)`).join('|')}`) won't generate \\. correctly\n        const regexp = /(\\.|^)firstObject(\\.|$)|(\\.|^)lastObject(\\.|$)/;\n\n        if (typeof node.value === 'string' && regexp.test(node.value)) {\n          context.report({ node, messageId: 'main' });\n        }\n      },\n\n      ClassDeclaration(node) {\n        // Keep track of class properties to ignore because we know they were initialized to an instance of a non-array class.\n        const classPropertiesToIgnore = new Set(\n          node.body.body\n            .filter(\n              (n) =>\n                // ClassProperty / ClassPrivateProperty / PrivateName are for ESLint v7.\n                (isClassPropertyOrPropertyDefinition(n) || n.type === 'ClassPrivateProperty') &&\n                ['Identifier', 'PrivateIdentifier', 'PrivateName'].includes(n.key.type) &&\n                n.value &&\n                n.value.type === 'NewExpression' &&\n                n.value.callee.type === 'Identifier' &&\n                KNOWN_NON_ARRAY_CLASSES.has(n.value.callee.name)\n            )\n            .map((n) => (n.key.type === 'Identifier' ? n.key.name : `#${n.key.name}`)) // Add # for private properties to avoid confusing public/private properties.\n        );\n\n        classStack.push({ node, classPropertiesToIgnore });\n      },\n\n      'ClassDeclaration:exit'() {\n        // Leaving the current class.\n        classStack.pop();\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-arrow-function-computed-properties.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Do not use arrow functions in computed properties';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow arrow functions in computed properties',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-arrow-function-computed-properties.md',\n    },\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          onlyThisContexts: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Whether the rule should allow or disallow computed properties where the arrow function body does not contain a `this` reference.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const onlyThisContexts = options.onlyThisContexts || false;\n\n    let isThisPresent = false;\n\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      ThisExpression() {\n        isThisPresent = true;\n      },\n      CallExpression() {\n        isThisPresent = false;\n      },\n      'CallExpression:exit'(node) {\n        const isComputedArrow =\n          emberUtils.isComputedProp(node, importedEmberName, importedComputedName, {\n            includeMacro: true,\n          }) &&\n          node.arguments.length > 0 &&\n          types.isArrowFunctionExpression(node.arguments.at(-1));\n\n        if (!isComputedArrow) {\n          return;\n        }\n\n        if (onlyThisContexts) {\n          if (isThisPresent) {\n            context.report({\n              node: node.arguments.at(-1),\n              message: ERROR_MESSAGE,\n            });\n          }\n        } else {\n          context.report({\n            node: node.arguments.at(-1),\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst types = require('../utils/types');\nconst decoratorUtils = require('../utils/decorators');\nconst propertySetterUtils = require('../utils/property-setter');\nconst { getImportIdentifier } = require('../utils/import');\nconst { DEFAULT_MACRO_CONFIGURATIONS } = require('../utils/computed-property-macros');\nconst {\n  findComputedPropertyDependentKeys,\n  keyExistsAsPrefixInList,\n} = require('../utils/computed-property-dependent-keys');\nconst Stack = require('../utils/stack');\n\nfunction createMacrosByPathMap(macroConfigurations) {\n  const macrosByPath = new Map();\n\n  for (const config of macroConfigurations) {\n    if (!macrosByPath.has(config.path)) {\n      macrosByPath.set(config.path, new Map());\n    }\n\n    macrosByPath.get(config.path).set(config.name, config);\n  }\n\n  return macrosByPath;\n}\n\nfunction createMacrosByIndexPathMap(macroConfigurations) {\n  const macrosByIndexPath = new Map();\n\n  for (const config of macroConfigurations) {\n    if (!macrosByIndexPath.has(config.indexPath)) {\n      macrosByIndexPath.set(config.indexPath, new Map());\n    }\n\n    if (!macrosByIndexPath.get(config.indexPath).has(config.indexName)) {\n      macrosByIndexPath.get(config.indexPath).set(config.indexName, new Map());\n    }\n\n    macrosByIndexPath.get(config.indexPath).get(config.indexName).set(config.name, config);\n  }\n\n  return macrosByIndexPath;\n}\n\nconst ERROR_MESSAGE =\n  \"Use `set(this, 'propertyName', 'value')` instead of assignment for untracked properties that are used as computed property dependencies (or convert to using tracked properties).\";\n\n/**\n * Gets a set of tracked properties used inside a class.\n *\n * @param {Node} nodeClass - Node for the class\n * @returns {Set<string>} - set of tracked properties used inside the class\n */\nfunction findTrackedProperties(nodeClassDeclaration, trackedImportName) {\n  return new Set(\n    nodeClassDeclaration.body.body\n      .filter(\n        (node) =>\n          types.isClassPropertyOrPropertyDefinition(node) &&\n          decoratorUtils.hasDecorator(node, trackedImportName) &&\n          (types.isIdentifier(node.key) || types.isStringLiteral(node.key))\n      )\n      .map((node) => node.key.name || node.key.value)\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'disallow assignment of untracked properties that are used as computed property dependencies',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          extraMacros: {\n            type: 'array',\n            uniqueItems: true,\n            minItems: 1,\n            items: {\n              type: 'object',\n              required: ['path', 'name'],\n              dependencies: {\n                indexPath: ['indexName'],\n                indexName: ['indexPath'],\n              },\n              properties: {\n                path: {\n                  type: 'string',\n                  minLength: 1,\n                  // Extra macros cannot be in @ember/\n                  pattern: '^(?!@ember/).+/',\n                },\n                name: {\n                  type: 'string',\n                  minLength: 1,\n                },\n                indexPath: {\n                  type: 'string',\n                  minLength: 1,\n                  // Extra macros cannot be in @ember/\n                  pattern: '^(?!@ember/).+/',\n                },\n                indexName: {\n                  type: 'string',\n                  minLength: 1,\n                },\n                argumentFormat: {\n                  type: 'array',\n                  uniqueItems: true,\n                  minItems: 1,\n                  items: {\n                    type: 'object',\n                    properties: {\n                      strings: {\n                        type: 'object',\n                        properties: {\n                          startIndex: {\n                            type: 'number',\n                            multipleOf: 1,\n                            default: 0,\n                            minimum: 0,\n                          },\n                          count: {\n                            type: 'number',\n                            default: Number.MAX_VALUE,\n                            multipleOf: 1,\n                            minimum: 1,\n                          },\n                        },\n                        additionalProperties: false,\n                      },\n                      objects: {\n                        type: 'object',\n                        required: ['index'],\n                        properties: {\n                          keys: {\n                            type: 'array',\n                            uniqueItems: true,\n                            minItems: 1,\n                            items: {\n                              type: 'string',\n                              minLength: 1,\n                            },\n                          },\n                          index: {\n                            type: 'number',\n                            multipleOf: 1,\n                            minimum: 0,\n                          },\n                        },\n                        additionalProperties: false,\n                      },\n                    },\n                    additionalProperties: false,\n                  },\n                },\n              },\n              additionalProperties: false,\n            },\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    if (emberUtils.isTestFile(context.filename)) {\n      // This rule does not apply to test files.\n      return {};\n    }\n\n    const options = context.options[0] || { extraMacros: [] };\n\n    const macroConfigurations = [...DEFAULT_MACRO_CONFIGURATIONS, ...options.extraMacros];\n\n    const macrosByPath = createMacrosByPathMap(macroConfigurations);\n    const macrosByIndexPath = createMacrosByIndexPathMap(macroConfigurations);\n\n    // State being tracked for this file.\n    let trackedImportName = undefined;\n    let computedImportName = undefined;\n    let setImportName = undefined;\n    let macrosByName = new Map();\n    let macrosByIndexName = new Map();\n\n    // State being tracked for the current class we're inside.\n    const classStack = new Stack();\n\n    return {\n      ImportDeclaration(node) {\n        const importPath = node.source.value;\n\n        // Checking for something like `import { readOnly } from '@ember/object/computed;`\n        if (macrosByPath.has(importPath)) {\n          const newMacroImportNameEntries = [...macrosByPath.get(importPath)]\n            .map(([name, config]) => [getImportIdentifier(node, importPath, name), config])\n            .filter(([importedName]) => importedName);\n          macrosByName = new Map([...macrosByName, ...newMacroImportNameEntries]);\n        }\n\n        // Checking for something like `import { computed } from '@ember/object;`\n        if (macrosByIndexPath.has(importPath)) {\n          const newMacroIndexImportEntries = [...macrosByIndexPath.get(importPath)]\n            .map(([indexName, propertyConfigs]) => [\n              getImportIdentifier(node, importPath, indexName),\n              propertyConfigs,\n            ])\n            .filter(([importedName]) => importedName);\n\n          macrosByIndexName = new Map([...macrosByIndexName, ...newMacroIndexImportEntries]);\n        }\n\n        if (node.source.value === '@ember/object') {\n          setImportName = setImportName || getImportIdentifier(node, '@ember/object', 'set');\n          computedImportName =\n            computedImportName || getImportIdentifier(node, '@ember/object', 'computed');\n        } else if (node.source.value === '@glimmer/tracking') {\n          trackedImportName =\n            trackedImportName || getImportIdentifier(node, '@glimmer/tracking', 'tracked');\n        }\n      },\n\n      // Native JS class:\n      ClassDeclaration(node) {\n        // Gather computed property dependent keys from this class.\n        const computedPropertyDependentKeys = findComputedPropertyDependentKeys(\n          node,\n          computedImportName,\n          macrosByName,\n          macrosByIndexName\n        );\n\n        // Gather tracked properties from this class.\n        const trackedProperties = findTrackedProperties(node, trackedImportName);\n\n        // Keep track of whether we're inside a Glimmer component.\n        const isGlimmerComponent = emberUtils.isGlimmerComponent(context, node);\n\n        classStack.push({\n          node,\n          computedPropertyDependentKeys,\n          trackedProperties,\n          isGlimmerComponent,\n        });\n      },\n\n      CallExpression(node) {\n        // Classic class:\n        if (emberUtils.isAnyEmberCoreModule(context, node)) {\n          // Gather computed property dependent keys from this class.\n          const computedPropertyDependentKeys = findComputedPropertyDependentKeys(\n            node,\n            computedImportName,\n            macrosByName,\n            macrosByIndexName\n          );\n\n          // No tracked properties in classic classes.\n          const trackedProperties = new Set();\n\n          // Keep track of whether we're inside a Glimmer component.\n          const isGlimmerComponent = emberUtils.isGlimmerComponent(context, node);\n\n          classStack.push({\n            node,\n            computedPropertyDependentKeys,\n            trackedProperties,\n            isGlimmerComponent,\n          });\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        if (classStack.size() > 0 && classStack.peek().node === node) {\n          // Leaving current (native) class.\n          classStack.pop();\n        }\n      },\n\n      'CallExpression:exit'(node) {\n        if (classStack.size() > 0 && classStack.peek().node === node) {\n          // Leaving current (classic) class.\n          classStack.pop();\n        }\n      },\n\n      AssignmentExpression(node) {\n        if (classStack.size() === 0) {\n          // Not inside a class.\n          return;\n        }\n\n        // Ensure this is an assignment with `this.x = ` or `this.x.y = `.\n        if (!propertySetterUtils.isThisSet(node)) {\n          return;\n        }\n\n        const currentClass = classStack.peek();\n\n        const sourceCode = context.sourceCode;\n        const nodeTextLeft = sourceCode.getText(node.left);\n        const nodeTextRight = sourceCode.getText(node.right);\n        const propertyName = nodeTextLeft.replace('this.', '');\n\n        if (currentClass.isGlimmerComponent && propertyName.startsWith('args.')) {\n          // The Glimmer component args hash is automatically tracked so ignored it.\n          return;\n        }\n\n        if (\n          !currentClass.computedPropertyDependentKeys.has(propertyName) &&\n          !keyExistsAsPrefixInList(\n            [...currentClass.computedPropertyDependentKeys.keys()],\n            propertyName\n          )\n        ) {\n          // Haven't seen this property as a computed property dependent key so ignore it.\n          return;\n        }\n\n        if (currentClass.trackedProperties.has(propertyName)) {\n          // Assignment is fine with tracked properties so ignore it.\n          return;\n        }\n\n        context.report({\n          node,\n          message: ERROR_MESSAGE,\n          fix(fixer) {\n            if (setImportName) {\n              // `set` is already imported.\n              return fixer.replaceText(\n                node,\n                `${setImportName}(this, '${propertyName}', ${nodeTextRight})`\n              );\n            } else {\n              // Need to add an import statement for `set`.\n              const sourceCode = context.sourceCode;\n              return [\n                fixer.insertTextBefore(sourceCode.ast, \"import { set } from '@ember/object';\\n\"),\n                fixer.replaceText(node, `set(this, '${propertyName}', ${nodeTextRight})`),\n              ];\n            }\n          },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-at-ember-render-modifiers.js",
    "content": "'use strict';\n\nconst ERROR_MESSAGE =\n  'Do not use @ember/render-modifiers. Instead, use derived data patterns, and/or co-locate destruction via @ember/destroyable';\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nfunction importDeclarationIsPackageName(node, path) {\n  return node.source.value === path || node.source.value.startsWith(`${path}/`);\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow importing from @ember/render-modifiers',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-at-ember-render-modifiers.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      ImportDeclaration(node) {\n        if (importDeclarationIsPackageName(node, '@ember/render-modifiers')) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-attrs-in-components.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst { isEmberComponent, isGlimmerComponent } = require('../utils/ember');\n\nconst ERROR_MESSAGE = 'Do not use `this.attrs`';\n\n//------------------------------------------------------------------------------\n// General rule - Don't use this.attrs\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of `this.attrs` in components',\n      category: 'Components',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-attrs-in-components.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let currentEmberComponent = null;\n\n    return {\n      ClassDeclaration(node) {\n        if (isEmberComponent(context, node) || isGlimmerComponent(context, node)) {\n          currentEmberComponent = node;\n        }\n      },\n\n      CallExpression(node) {\n        if (isEmberComponent(context, node)) {\n          currentEmberComponent = node;\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        if (currentEmberComponent === node) {\n          currentEmberComponent = null;\n        }\n      },\n\n      'CallExpression:exit'(node) {\n        if (currentEmberComponent === node) {\n          currentEmberComponent = null;\n        }\n      },\n\n      MemberExpression(node) {\n        if (currentEmberComponent && isThisAttrsExpression(node)) {\n          context.report({ node: node.property, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n\nfunction isThisAttrsExpression(node) {\n  return types.isThisExpression(node.object) && node.property.name === 'attrs';\n}\n"
  },
  {
    "path": "lib/rules/no-attrs-snapshot.js",
    "content": "'use strict';\n\nconst utils = require('../utils/utils');\n\n//------------------------------------------------------------------------------\n// General rule -  Disallow use of attrs snapshot in `didReceiveAttrs`\n// and `didUpdateAttrs`.\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE =\n  'Do not use the attrs snapshot that is passed in `didReceiveAttrs` and `didUpdateAttrs`.';\n\nconst hasAttrsSnapShot = function (node) {\n  const methodName = utils.getPropertyValue(node, 'parent.key.name');\n  const hasParams = node.params.length > 0;\n\n  return (methodName === 'didReceiveAttrs' || methodName === 'didUpdateAttrs') && hasParams;\n};\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'disallow use of attrs snapshot in the `didReceiveAttrs` and `didUpdateAttrs` component hooks',\n      category: 'Components',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-attrs-snapshot.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      FunctionExpression(node) {\n        if (hasAttrsSnapShot(node)) {\n          report(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-builtin-form-components.js",
    "content": "'use strict';\n\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Do not use built-in form components. Use native HTML elements instead.';\nconst DISALLOWED_IMPORTS = new Set(['Input', 'Textarea']);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of built-in form components',\n      category: 'Components',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-builtin-form-components.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/component') {\n          // Check for named imports like: import { Input } from '@ember/component';\n          const namedImports = node.specifiers.filter(\n            (specifier) =>\n              specifier.type === 'ImportSpecifier' &&\n              DISALLOWED_IMPORTS.has(specifier.imported.name)\n          );\n\n          if (namedImports.length > 0) {\n            for (const specifier of namedImports) {\n              report(specifier);\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-capital-letters-in-routes.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\n\n//------------------------------------------------------------------------------\n// Routing - No capital letters in routes\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow routes with uppercased letters in router.js',\n      category: 'Routes',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-capital-letters-in-routes.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: \"Unexpected capital letter in route's name\" });\n    };\n\n    return {\n      CallExpression(node) {\n        if (ember.isRoute(node) && node.arguments[0] && types.isLiteral(node.arguments[0])) {\n          const routeName = node.arguments[0].value;\n          const hasAnyUppercaseLetter = Boolean(routeName.match('[A-Z]'));\n\n          if (hasAnyUppercaseLetter) {\n            report(node);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-classic-classes.js",
    "content": "'use strict';\n\nconst { getSourceModuleNameForIdentifier } = require('../utils/import');\nconst { startsWithThisExpression } = require('../utils/utils');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\n\nconst ERROR_MESSAGE_NO_CLASSIC_CLASSES =\n  'Native JS classes should be used instead of classic classes';\n\nfunction hasNoArguments(node) {\n  return node.arguments.length === 0;\n}\n\nfunction hasObjectArgument(node, scopeManager) {\n  return node.arguments.some((arg) => {\n    const resultingNode = getNodeOrNodeFromVariable(arg, scopeManager);\n    return resultingNode && resultingNode.type === 'ObjectExpression';\n  });\n}\n\nfunction isEmberImport(classImportedFrom) {\n  if (!classImportedFrom) {\n    return false;\n  }\n\n  // Warn about classes imported from the `@ember/` and Ember Data namespaces\n  return (\n    classImportedFrom.startsWith('@ember/') ||\n    classImportedFrom.startsWith('@ember-data/') ||\n    classImportedFrom === 'ember-data'\n  );\n}\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE_NO_CLASSIC_CLASSES,\n\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow \"classic\" classes in favor of native JS classes',\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-classic-classes.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          additionalClassImports: {\n            type: 'array',\n            uniqueItems: true,\n            minItems: 1,\n            items: {\n              type: 'string',\n            },\n            description:\n              'Allows you to specify additional imports that should be flagged to disallow calling `extend` on. This allows you to handle the case where your app or addon is importing from a module that performs the `extend`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n    const options = context.options[0] || {};\n    const additionalClassImports = options.additionalClassImports || [];\n\n    function reportNode(node) {\n      context.report({ node, message: ERROR_MESSAGE_NO_CLASSIC_CLASSES });\n    }\n\n    return {\n      'CallExpression > MemberExpression[property.name=\"extend\"]'(node) {\n        const callExpression = node.parent;\n\n        // Invalid if there are no arguments because that is equivalent to not called `extend` at all\n        // Invalid with an object as an argument because that logic needs conversion to a native class\n        // Still allows `.extend` if some other identifier is passed, like a Mixin\n        if (\n          (hasNoArguments(callExpression) || hasObjectArgument(callExpression, scopeManager)) &&\n          ['Identifier', 'MemberExpression'].includes(node.object.type) &&\n          !startsWithThisExpression(node)\n        ) {\n          const classImportedFrom = getSourceModuleNameForIdentifier(context, node.object);\n          if (\n            isEmberImport(classImportedFrom) ||\n            additionalClassImports.includes(classImportedFrom)\n          ) {\n            reportNode(callExpression);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-classic-components.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst assert = require('assert');\n\nconst ERROR_MESSAGE =\n  'Use Glimmer components(@glimmer/component) instead of classic components(@ember/component)';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce using Glimmer components',\n      category: 'Components',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-classic-components.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/component') {\n          const importDefaultSpecifier = getImportDefaultSpecifier(node);\n          if (importDefaultSpecifier) {\n            report(importDefaultSpecifier);\n          }\n        }\n      },\n    };\n  },\n};\n\nfunction getImportDefaultSpecifier(node) {\n  assert(types.isImportDeclaration(node));\n  return node.specifiers.find((specifier) => types.isImportDefaultSpecifier(specifier));\n}\n"
  },
  {
    "path": "lib/rules/no-component-lifecycle-hooks.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\n\nconst {\n  isComponentLifecycleHook,\n  isGlimmerComponentLifecycleHook,\n  isEmberComponent,\n  isGlimmerComponent,\n} = emberUtils;\n\nconst ERROR_MESSAGE_NO_COMPONENT_LIFECYCLE_HOOKS =\n  'Do not use classic ember components lifecycle hooks. Prefer using \"@ember/render-modifiers\" or custom functional modifiers.';\n\nconst report = (context, node) => {\n  context.report(node, ERROR_MESSAGE_NO_COMPONENT_LIFECYCLE_HOOKS);\n};\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE_NO_COMPONENT_LIFECYCLE_HOOKS,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'disallow usage of \"classic\" ember component lifecycle hooks. Render modifiers or custom functional modifiers should be used instead.',\n      category: 'Components',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-component-lifecycle-hooks.md',\n    },\n    fixable: null, // or \"code\" or \"whitespace\"\n    schema: [],\n  },\n\n  create(context) {\n    let isInsideEmberComponent = false;\n    let isInsideGlimmerComponent = false;\n    let currentComponentNode = null;\n    let isInsideClassDeclaration = false;\n\n    return {\n      // Native class.\n      ClassDeclaration(node) {\n        if (isEmberComponent(context, node)) {\n          currentComponentNode = node;\n          isInsideEmberComponent = true;\n          isInsideClassDeclaration = true;\n        } else if (isGlimmerComponent(context, node)) {\n          currentComponentNode = node;\n          isInsideGlimmerComponent = true;\n          isInsideClassDeclaration = true;\n        }\n      },\n\n      // Classic class (not used by Glimmer components).\n      CallExpression(node) {\n        if (isEmberComponent(context, node) && !isInsideClassDeclaration) {\n          currentComponentNode = node;\n          isInsideEmberComponent = true;\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        if (currentComponentNode === node) {\n          currentComponentNode = null;\n          isInsideEmberComponent = false;\n          isInsideGlimmerComponent = false;\n          isInsideClassDeclaration = false;\n        }\n      },\n\n      'CallExpression:exit'(node) {\n        if (currentComponentNode === node && !isInsideClassDeclaration) {\n          currentComponentNode = null;\n          isInsideEmberComponent = false;\n        }\n      },\n\n      MethodDefinition(node) {\n        if (\n          isInsideEmberComponent &&\n          isComponentLifecycleHook(node) &&\n          !isGlimmerComponentLifecycleHook(node)\n        ) {\n          // Classic Ember component using classic component lifecycle hook.\n          report(context, node);\n        } else if (\n          isInsideGlimmerComponent &&\n          isComponentLifecycleHook(node) &&\n          !isGlimmerComponentLifecycleHook(node)\n        ) {\n          // Glimmer component using classic component lifecycle hook.\n          report(context, node);\n        }\n      },\n\n      Property(node) {\n        if (\n          isInsideEmberComponent &&\n          isComponentLifecycleHook(node) &&\n          !isGlimmerComponentLifecycleHook(node)\n        ) {\n          report(context, node.key);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-computed-properties-in-native-classes.js",
    "content": "'use strict';\n\nconst utils = require('../utils/utils');\nconst emberUtils = require('../utils/ember');\nconst { hasDecorator } = require('../utils/decorators');\n\nconst ERROR_MESSAGE =\n  \"Don't use computed properties with native classes. Use getters or @tracked properties instead.\";\n\n/**\n * Locates all the ImportDeclaration with computed properties.\n * @param {Node[]} nodeBody Array of nodes.\n * @returns {Node[]} Array of nodes with .\n */\nfunction findComputedNodes(nodeBody) {\n  const importDeclarations = utils.findNodes(nodeBody, 'ImportDeclaration');\n  return importDeclarations.filter((node) => {\n    return (\n      node.source.value === '@ember/object/computed' ||\n      (node.source.value === '@ember/object' &&\n        node.specifiers.some((s) => (s.imported ? s.imported.name : s.local.name) === 'computed'))\n    );\n  });\n}\n\n//----------------------------------------------\n// General rule - Do not use computed properties in native classes\n//----------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow using computed properties in native classes',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-computed-properties-in-native-classes.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          ignoreClassic: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should ignore usage inside of native classes labeled with `@classic`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const ignoreClassic = !context.options[0] || context.options[0].ignoreClassic;\n    let computedNodes = [];\n    let hasClassicExtend = false;\n    const nativeClasses = [];\n\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      Program(node) {\n        computedNodes = findComputedNodes(node.body);\n      },\n      ClassDeclaration(node) {\n        if (!ignoreClassic || !hasDecorator(node, 'classic')) {\n          nativeClasses.push(node);\n        }\n      },\n      CallExpression(node) {\n        if (emberUtils.isAnyEmberCoreModule(context, node)) {\n          hasClassicExtend = true;\n        }\n      },\n      'Program:exit'() {\n        // Only report if there are native classes and no classic .extend() calls,\n        // since classic classes legitimately use computed properties.\n        if (nativeClasses.length > 0 && !hasClassicExtend) {\n          for (const importNode of computedNodes) {\n            report(importNode);\n          }\n        }\n      },\n    };\n  },\n\n  ERROR_MESSAGE,\n};\n"
  },
  {
    "path": "lib/rules/no-controller-access-in-routes.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE =\n  'Do not access controller in route outside of setupController/resetController';\n\n//------------------------------------------------------------------------------\n// Routing - No controller access in routes\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'disallow routes from accessing the controller outside of setupController/resetController',\n      category: 'Routes',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-controller-access-in-routes.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowControllerFor: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Whether the rule should allow or disallow routes from accessing the controller outside of `setupController`/`resetController` via `controllerFor`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const allowControllerFor = context.options[0] && context.options[0].allowControllerFor;\n    let importedGetName;\n    let importedGetPropertiesName;\n    let currentRouteNode = null;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object') {\n          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');\n          importedGetPropertiesName =\n            importedGetPropertiesName ||\n            getImportIdentifier(node, '@ember/object', 'getProperties');\n        }\n      },\n\n      ClassDeclaration(node) {\n        if (ember.isEmberRoute(context, node)) {\n          currentRouteNode = node;\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        if (currentRouteNode === node) {\n          currentRouteNode = null;\n        }\n      },\n\n      MemberExpression(node) {\n        if (!currentRouteNode) {\n          return;\n        }\n\n        if (\n          types.isThisExpression(node.object) &&\n          types.isIdentifier(node.property) &&\n          node.property.name === 'controller'\n        ) {\n          // Example: this.controller;\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n\n      // eslint-disable-next-line complexity\n      CallExpression(node) {\n        if (ember.isEmberRoute(context, node)) {\n          currentRouteNode = node;\n        }\n\n        if (!currentRouteNode) {\n          return;\n        }\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          types.isThisExpression(node.callee.object) &&\n          types.isIdentifier(node.callee.property) &&\n          !allowControllerFor &&\n          node.callee.property.name === 'controllerFor'\n        ) {\n          // Example this.controllerFor(...);\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          types.isThisExpression(node.callee.object) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'get' &&\n          node.arguments.length === 1 &&\n          types.isStringLiteral(node.arguments[0]) &&\n          node.arguments[0].value === 'controller'\n        ) {\n          // Example: this.get('controller');\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetName &&\n          node.arguments.length === 2 &&\n          types.isThisExpression(node.arguments[0]) &&\n          types.isStringLiteral(node.arguments[1]) &&\n          node.arguments[1].value === 'controller'\n        ) {\n          // Example: get(this, 'controller');\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          types.isThisExpression(node.callee.object) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'getProperties' &&\n          getPropertiesArgumentsIncludeController(node.arguments)\n        ) {\n          // Example: this.getProperties('controller');\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetPropertiesName &&\n          types.isThisExpression(node.arguments[0]) &&\n          getPropertiesArgumentsIncludeController(node.arguments.slice(1))\n        ) {\n          // Example: getProperties(this, 'controller');\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n\n      'CallExpression:exit'(node) {\n        if (currentRouteNode === node) {\n          currentRouteNode = null;\n        }\n      },\n\n      VariableDeclarator(node) {\n        if (!currentRouteNode) {\n          return;\n        }\n\n        if (\n          !node.init ||\n          node.init.type !== 'ThisExpression' ||\n          !node.id ||\n          node.id.type !== 'ObjectPattern'\n        ) {\n          return;\n        }\n\n        const controllerProperty = node.id.properties.find(\n          (prop) => prop.key.type === 'Identifier' && prop.key.name === 'controller'\n        );\n        if (controllerProperty) {\n          // Example: const { controller } = this;\n          context.report({ node: controllerProperty, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n\nfunction getPropertiesArgumentsIncludeController(args) {\n  if (args.length === 1 && types.isArrayExpression(args[0])) {\n    return getPropertiesArgumentsIncludeController(args[0].elements);\n  }\n  return args.find(\n    (argument) => types.isStringLiteral(argument) && argument.value === 'controller'\n  );\n}\n"
  },
  {
    "path": "lib/rules/no-controllers.js",
    "content": "const ember = require('../utils/ember');\nconst types = require('../utils/types');\nconst utils = require('../utils/utils');\nconst assert = require('assert');\n\nconst ERROR_MESSAGE = 'Avoid using controllers except for specifying `queryParams`';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow non-essential controllers',\n      category: 'Controllers',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-controllers.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create: (context) => {\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      ClassDeclaration(node) {\n        if (\n          ember.isEmberController(context, node) &&\n          (node.body.body.length === 0 || !classDeclarationHasProperty(node, 'queryParams'))\n        ) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          ember.isEmberController(context, node) &&\n          (node.arguments.length === 0 ||\n            !callExpressionClassHasProperty(node, 'queryParams', scopeManager))\n        ) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n\nfunction classDeclarationHasProperty(classDeclaration, propertyName) {\n  assert(types.isClassDeclaration(classDeclaration));\n  return classDeclaration.body.body.some(\n    (item) =>\n      types.isClassPropertyOrPropertyDefinition(item) &&\n      ((types.isIdentifier(item.key) && item.key.name === propertyName) ||\n        (types.isStringLiteral(item.key) && item.key.value === propertyName))\n  );\n}\n\nfunction callExpressionClassHasProperty(callExpression, propertyName, scopeManager) {\n  assert(types.isCallExpression(callExpression));\n  return callExpression.arguments.some((arg) => {\n    const resultingNode = utils.getNodeOrNodeFromVariable(arg, scopeManager);\n    return (\n      resultingNode &&\n      resultingNode.type === 'ObjectExpression' &&\n      resultingNode.properties.some(\n        (prop) =>\n          (types.isIdentifier(prop.key) && prop.key.name === propertyName) ||\n          (types.isStringLiteral(prop.key) && prop.key.value === propertyName)\n      )\n    );\n  });\n}\n"
  },
  {
    "path": "lib/rules/no-current-route-name.js",
    "content": "'use strict';\n\nconst ERROR_MESSAGE = 'Use currentURL() instead of currentRouteName()';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of the `currentRouteName()` test helper',\n      category: 'Testing',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-current-route-name.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const importAliases = [];\n\n    return {\n      ImportSpecifier(node) {\n        const { imported, local } = node;\n        if (\n          imported.type === 'Identifier' &&\n          imported.name === 'currentRouteName' &&\n          local.type === 'Identifier'\n        ) {\n          importAliases.push(local.name);\n        }\n      },\n\n      CallExpression(node) {\n        const { callee } = node;\n        if (callee.type === 'Identifier' && importAliases.includes(callee.name)) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-deeply-nested-dependent-keys-with-each.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE =\n  'Dependent keys containing `@each` only work one level deep. You cannot use nested forms like: `todos.@each.owner.name`. Please create an intermediary computed property instead.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of deeply-nested computed property dependent keys with `@each`',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-deeply-nested-dependent-keys-with-each.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      CallExpression(node) {\n        if (emberUtils.isComputedProp(node, importedEmberName, importedComputedName)) {\n          for (const key of emberUtils.parseDependentKeys(node)) {\n            const parts = key.split('.');\n            const indexOfAtEach = parts.indexOf('@each');\n            if (indexOfAtEach < 0) {\n              continue;\n            }\n            if (parts.length > indexOfAtEach + 2) {\n              context.report({\n                node,\n                message: ERROR_MESSAGE,\n              });\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-deprecated-router-transition-methods.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\nconst Stack = require('../utils/stack');\nconst types = require('../utils/types');\nconst decoratorUtils = require('../utils/decorators');\n\nfunction getBaseFixSteps(fixer, context, currentClass) {\n  const fixSteps = [];\n  const sourceCode = context.sourceCode;\n  let serviceInjectImportName = currentClass.serviceInjectImportName;\n  let routerServicePropertyName = currentClass.routerServicePropertyName;\n\n  // For now, we insert the legacy form. If we can detect the Ember version we can insert the new version instead.\n  if (!serviceInjectImportName) {\n    fixSteps.push(\n      fixer.insertTextBefore(\n        sourceCode.ast,\n        \"import { inject as service } from '@ember/service';\\n\"\n      )\n    );\n\n    serviceInjectImportName = 'service';\n  }\n\n  if (!routerServicePropertyName) {\n    if (currentClass.node.type === 'CallExpression') {\n      const lastArg = currentClass.node.arguments.at(-1);\n      if (lastArg && lastArg.type === 'ObjectExpression' && lastArg.properties.length > 0) {\n        fixSteps.push(\n          fixer.insertTextBefore(\n            lastArg.properties[0],\n            `router: ${serviceInjectImportName}('router'),\\n`\n          )\n        );\n        routerServicePropertyName = 'router';\n      }\n    } else if (currentClass.node.body.body.length > 0) {\n      fixSteps.push(\n        fixer.insertTextBefore(\n          currentClass.node.body.body[0],\n          `@${serviceInjectImportName}('router') router;\\n`\n        )\n      );\n      routerServicePropertyName = 'router';\n    }\n  }\n\n  return { fixSteps, routerServicePropertyName };\n}\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of router service transition methods',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-deprecated-router-transition-methods.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      main: 'Calling \"{{methodUsed}}\" in {{moduleType}}s is deprecated, call {{desiredMethod}} on the injected router service instead.',\n    },\n  },\n\n  create(context) {\n    // State being tracked for this file.\n    let serviceInjectImportName = undefined;\n    let routerServicePropertyName = undefined;\n    let isValidModule = false;\n\n    // State being tracked for the current class we're inside.\n    const classStack = new Stack();\n\n    function onClassEnter(node) {\n      if (emberUtils.isAnyEmberCoreModule(context, node)) {\n        const classMembers = node.body.body;\n\n        if (serviceInjectImportName) {\n          for (const classMember of classMembers) {\n            if (emberUtils.isInjectedServiceProp(classMember, undefined, serviceInjectImportName)) {\n              const serviceExpression = decoratorUtils.findDecorator(\n                classMember,\n                serviceInjectImportName\n              ).expression;\n\n              if (serviceExpression.type === 'CallExpression') {\n                if (\n                  (serviceExpression.arguments.length === 0 && classMember.key.name === 'router') ||\n                  (serviceExpression.arguments.length > 0 &&\n                    serviceExpression.arguments[0].value === 'router')\n                ) {\n                  routerServicePropertyName = classMember.key.name;\n                }\n              } else if (classMember.key.name === 'router') {\n                routerServicePropertyName = classMember.key.name;\n              }\n            }\n          }\n        }\n\n        const isRoute = emberUtils.isEmberRoute(context, node);\n        const isController = emberUtils.isEmberController(context, node);\n        isValidModule = isRoute || isController;\n\n        classStack.push({\n          node,\n          serviceInjectImportName,\n          routerServicePropertyName,\n          isValidModule,\n          isRoute,\n          isController,\n        });\n      } else {\n        classStack.push({\n          node,\n          isValidModule: false,\n        });\n      }\n    }\n\n    function onClassExit(node) {\n      // Leaving current (native) class.\n      if (classStack.size() > 0 && classStack.peek().node === node) {\n        classStack.pop();\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/service') {\n          serviceInjectImportName =\n            serviceInjectImportName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n\n      ClassDeclaration: onClassEnter,\n      ClassExpression: onClassEnter,\n      CallExpression(node) {\n        if (emberUtils.isAnyEmberCoreModule(context, node)) {\n          const lastExtendArg = node.arguments.at(-1);\n\n          if (lastExtendArg && lastExtendArg.type === 'ObjectExpression') {\n            const classMembers = lastExtendArg.properties;\n            for (const classMember of classMembers) {\n              if (\n                emberUtils.isInjectedServiceProp(classMember, undefined, serviceInjectImportName)\n              ) {\n                const callExpression = classMember.value;\n\n                if (\n                  (callExpression.arguments.length === 0 && classMember.key.name === 'router') ||\n                  (callExpression.arguments.length > 0 &&\n                    callExpression.arguments[0].value === 'router')\n                ) {\n                  routerServicePropertyName = classMember.key.name;\n                }\n              }\n            }\n            const isRoute = emberUtils.isEmberRoute(context, node);\n            const isController = emberUtils.isEmberController(context, node);\n            isValidModule = isRoute || isController;\n\n            classStack.push({\n              node,\n              serviceInjectImportName,\n              routerServicePropertyName,\n              isValidModule,\n              isRoute,\n              isController,\n            });\n          }\n        }\n      },\n\n      'ClassDeclaration:exit': onClassExit,\n      'ClassExpression:exit': onClassExit,\n      'CallExpression:exit': onClassExit,\n\n      MemberExpression(node) {\n        if (!isValidModule) {\n          return;\n        }\n\n        const currentClass = classStack.peek();\n\n        if (!currentClass) {\n          return;\n        }\n\n        if (types.isThisExpression(node.object) && types.isIdentifier(node.property)) {\n          // Routes should not call transitionTo or replaceWith\n          const propertyName = node.property.name;\n\n          if (\n            currentClass.isRoute &&\n            (propertyName === 'transitionTo' || propertyName === 'replaceWith')\n          ) {\n            context.report({\n              node,\n              messageId: 'main',\n              data: {\n                methodUsed: propertyName,\n                desiredMethod: propertyName,\n                moduleType: 'Route',\n              },\n              fix(fixer) {\n                const { routerServicePropertyName, fixSteps } = getBaseFixSteps(\n                  fixer,\n                  context,\n                  currentClass\n                );\n\n                return [\n                  ...fixSteps,\n                  fixer.insertTextBefore(node.property, `${routerServicePropertyName}.`),\n                ];\n              },\n            });\n          }\n\n          if (\n            currentClass.isController &&\n            (propertyName === 'transitionToRoute' || propertyName === 'replaceRoute')\n          ) {\n            const replacementPropertyName =\n              propertyName === 'transitionToRoute' ? 'transitionTo' : 'replaceWith';\n            context.report({\n              node,\n              messageId: 'main',\n              data: {\n                methodUsed: propertyName,\n                desiredMethod: replacementPropertyName,\n                moduleType: 'Controller',\n              },\n              fix(fixer) {\n                const { routerServicePropertyName, fixSteps } = getBaseFixSteps(\n                  fixer,\n                  context,\n                  currentClass\n                );\n\n                return [\n                  ...fixSteps,\n                  fixer.replaceText(\n                    node.property,\n                    `${routerServicePropertyName}.${replacementPropertyName}`\n                  ),\n                ];\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-duplicate-dependent-keys.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst types = require('../utils/types');\nconst fixerUtils = require('../utils/fixer');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Dependent keys should not be repeated';\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow repeating computed property dependent keys',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-duplicate-dependent-keys.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      CallExpression(node) {\n        if (emberUtils.hasDuplicateDependentKeys(node, importedEmberName, importedComputedName)) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n            fix(fixer) {\n              const sourceCode = context.sourceCode;\n\n              const stringNodes = node.arguments.filter((arg) => types.isStringLiteral(arg));\n              const duplicateNodes = findDuplicateStringNodes(stringNodes);\n\n              if (duplicateNodes.length === 0) {\n                return null;\n              }\n\n              return duplicateNodes.flatMap((duplicateNode) =>\n                fixerUtils.removeCommaSeparatedNode(duplicateNode, sourceCode, fixer)\n              );\n            },\n          });\n        }\n      },\n    };\n  },\n};\n\nfunction findDuplicateStringNodes(stringNodes) {\n  const seenNodes = new Set();\n  const duplicateNodes = [];\n\n  for (const node of stringNodes) {\n    if (seenNodes.has(node.value)) {\n      duplicateNodes.push(node);\n    } else {\n      seenNodes.add(node.value);\n    }\n  }\n\n  return duplicateNodes;\n}\n"
  },
  {
    "path": "lib/rules/no-ember-super-in-es-classes.js",
    "content": "'use strict';\n\nfunction isDirectlyInClass(node) {\n  let currentNode = node.parent;\n  let inFunctionAlready = false;\n\n  while (currentNode) {\n    if (currentNode.type === 'MethodDefinition') {\n      return true;\n    } else if (currentNode.type === 'FunctionExpression') {\n      if (inFunctionAlready) {\n        return false;\n      } else {\n        inFunctionAlready = true;\n      }\n    }\n\n    currentNode = currentNode.parent;\n  }\n\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow use of `this._super` in ES class methods',\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-ember-super-in-es-classes.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  create(context) {\n    return {\n      'MethodDefinition MemberExpression[object.type=\"ThisExpression\"][property.name=\"_super\"]'(\n        node\n      ) {\n        if (!isDirectlyInClass(node)) {\n          return;\n        }\n\n        context.report({\n          node,\n          message: \"Don't use `this._super` in ES classes; instead, you should use `super`\",\n          fix(fixer) {\n            let method = node;\n            while (method.type !== 'MethodDefinition') {\n              method = method.parent;\n            }\n\n            if (method.key.type === 'Identifier') {\n              return fixer.replaceText(node, `super.${method.key.name}`);\n            }\n\n            const text = context.sourceCode.getText(method.key);\n            return fixer.replaceText(node, `super[${text}]`);\n          },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-ember-testing-in-module-scope.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst utils = require('../utils/utils');\n\nconst ERROR_MESSAGES = [\n  'Ember.testing is not set in module scope',\n  'Ember.testing should not be assigned to a variable, use in place instead',\n  'Can not use destructuring to reference Ember.testing',\n];\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow use of `Ember.testing` in module scope',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-ember-testing-in-module-scope.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGES,\n\n  create(context) {\n    let emberImportAliasName = 'Ember';\n    let hasFoundImport = false;\n\n    return {\n      ImportDeclaration(node) {\n        if (!hasFoundImport) {\n          const aliasName = ember.getEmberImportAliasName(node);\n          if (aliasName) {\n            emberImportAliasName = aliasName;\n            hasFoundImport = true;\n          }\n        }\n      },\n\n      'Identifier[name=\"testing\"]'(node) {\n        if (\n          node.parent.type === 'MemberExpression' &&\n          node.parent.object.name === emberImportAliasName\n        ) {\n          const sourceCode = context.sourceCode;\n          const scope = sourceCode.getScope(node);\n\n          if (scope.variableScope.type === 'module') {\n            context.report({ node: node.parent, message: ERROR_MESSAGES[0] });\n          }\n          const nodeGrandParent = utils.getPropertyValue(node, 'parent.parent.type');\n          if (\n            nodeGrandParent === 'AssignmentExpression' ||\n            nodeGrandParent === 'VariableDeclarator'\n          ) {\n            context.report({ node: node.parent.parent, message: ERROR_MESSAGES[1] });\n          }\n        }\n      },\n\n      'Property[key.name=\"testing\"]'(node) {\n        if (\n          utils.getAncestor(\n            node,\n            (ancestorNode) =>\n              ancestorNode.type === 'VariableDeclarator' &&\n              ancestorNode.init.name === emberImportAliasName\n          )\n        ) {\n          context.report({ node: node.parent.parent.parent, message: ERROR_MESSAGES[2] });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-empty-attrs.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst decoratorUtils = require('../utils/decorators');\n\n//------------------------------------------------------------------------------\n// Ember Data - Be explicit with Ember data attribute types\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of empty attributes in Ember Data models',\n      category: 'Ember Data',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-empty-attrs.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    const message = 'Supply proper attribute type';\n    const filePath = context.filename;\n\n    const report = function (node) {\n      context.report({ node, message });\n    };\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    function reportNativeClassAttrs(node) {\n      for (const property of node.body.body) {\n        const attrDecorator = decoratorUtils.findDecorator(property, 'attr');\n\n        if (!attrDecorator) {\n          continue;\n        }\n\n        if (\n          attrDecorator.expression.type === 'Identifier' ||\n          (attrDecorator.expression.type === 'CallExpression' &&\n            attrDecorator.expression.arguments.length === 0)\n        ) {\n          report(attrDecorator.expression);\n        }\n      }\n    }\n\n    return {\n      CallExpression(node) {\n        if (!ember.isDSModel(node, filePath)) {\n          return;\n        }\n\n        const allProperties = ember.getModuleProperties(node, scopeManager);\n        const isDSAttr = allProperties.filter((property) =>\n          ember.isModule(property.value, 'attr', 'DS')\n        );\n\n        for (const attr of isDSAttr) {\n          if (attr.value.arguments.length === 0) {\n            report(attr.value);\n          }\n        }\n      },\n      ClassDeclaration(node) {\n        if (!ember.isEmberDataModel(context, node)) {\n          return;\n        }\n\n        reportNativeClassAttrs(node);\n      },\n      ClassExpression(node) {\n        if (!ember.isEmberDataModel(context, node)) {\n          return;\n        }\n\n        reportNativeClassAttrs(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-empty-glimmer-component-classes.js",
    "content": "'use strict';\n\nconst { isGlimmerComponent } = require('../utils/ember');\n\nconst ERROR_MESSAGE = 'Do not create empty backing classes for Glimmer components.';\nconst ERROR_MESSAGE_TEMPLATE_TAG =\n  'Do not create empty backing classes for Glimmer template tag only components.';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow empty backing classes for Glimmer components',\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-empty-glimmer-component-classes.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n  ERROR_MESSAGE_TEMPLATE_TAG,\n\n  create(context) {\n    return {\n      ClassDeclaration(node) {\n        if (node.declare || !isGlimmerComponent(context, node)) {\n          return;\n        }\n\n        const subClassHasTypeDefinition = node.typeParameters?.params?.length > 0;\n        if (!node.decorators?.length && node.body.body.length === 0 && !subClassHasTypeDefinition) {\n          context.report({ node, message: ERROR_MESSAGE });\n        } else if (\n          node.body.body.length === 1 &&\n          node.body.body[0].type === 'GlimmerTemplate' &&\n          !node.decorators?.length &&\n          !subClassHasTypeDefinition\n        ) {\n          context.report({ node, message: ERROR_MESSAGE_TEMPLATE_TAG });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-function-prototype-extensions.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\n\n//----------------------------------------------------------------------------------------------\n// General rule - Don't use Ember's function prototype extensions like .property() or .observe()\n//----------------------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: \"disallow usage of Ember's `function` prototype extensions\",\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-function-prototype-extensions.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    const message = \"Don't use Ember's function prototype extensions\";\n\n    const functionPrototypeExtensionNames = new Set([\n      'property',\n      'observes',\n      'observesBefore',\n      'on',\n    ]);\n\n    const isFunctionPrototypeExtension = function (property) {\n      return types.isIdentifier(property) && functionPrototypeExtensionNames.has(property.name);\n    };\n\n    const report = function (node) {\n      context.report({ node, message });\n    };\n\n    return {\n      CallExpression(node) {\n        const callee = node.callee;\n\n        if (\n          types.isCallExpression(node) &&\n          types.isMemberExpression(callee) &&\n          types.isFunctionExpression(callee.object) &&\n          isFunctionPrototypeExtension(callee.property)\n        ) {\n          report(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-get-with-default.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Use `||` or the ternary operator instead of `getWithDefault()`';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: \"disallow usage of the Ember's `getWithDefault` function\",\n      category: 'Ember Object',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-get-with-default.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          catchSafeObjects: {\n            type: 'boolean',\n            default: true,\n            description:\n              \"Whether the rule should catch non-`this` imported usages like `getWithDefault(person, 'name', '')`.\",\n          },\n          catchUnsafeObjects: {\n            type: 'boolean',\n            default: true,\n            description:\n              \"Whether the rule should catch non-`this` usages like `person.getWithDefault('name', '')` even though we don't know for sure if `person` is an Ember object.\",\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n  create(context) {\n    let importedGetName;\n    let importedGetWithDefaultName;\n\n    const catchSafeObjects = !context.options[0] || context.options[0].catchSafeObjects;\n    const catchUnsafeObjects = !context.options[0] || context.options[0].catchUnsafeObjects;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object') {\n          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');\n          importedGetWithDefaultName =\n            importedGetWithDefaultName ||\n            getImportIdentifier(node, '@ember/object', 'getWithDefault');\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          types.isMemberExpression(node.callee) &&\n          (types.isThisExpression(node.callee.object) || catchUnsafeObjects) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'getWithDefault' &&\n          node.arguments.length === 2\n        ) {\n          // Example: this.getWithDefault('foo', 'bar');\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n            fix(fixer) {\n              return fix({\n                fixer,\n                context,\n                node,\n                nodeObject: node.callee.object,\n                nodeProperty: node.arguments[0],\n                nodeDefault: node.arguments[1],\n                isImported: false,\n                importedGetName,\n              });\n            },\n          });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetWithDefaultName &&\n          node.arguments.length === 3 &&\n          (types.isThisExpression(node.arguments[0]) || catchSafeObjects)\n        ) {\n          // Example: getWithDefault(this, 'foo', 'bar');\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n            fix(fixer) {\n              return fix({\n                fixer,\n                context,\n                node,\n                nodeObject: node.arguments[0],\n                nodeProperty: node.arguments[1],\n                nodeDefault: node.arguments[2],\n                isImported: true,\n                importedGetName,\n              });\n            },\n          });\n        }\n      },\n    };\n  },\n};\n\n/**\n * @param {fixer} fixer\n * @param {context} context\n * @param {node} node - node with: this.getWithDefault('foo', 'bar');\n * @param {node} nodeObject - node with: 'this'\n * @param {node} nodeProperty - node with: 'foo'\n * @param {node} nodeDefault - node with: 'bar'\n * @param {boolean} isImported - whether we are dealing with the imported version of `getWithDefault`\n * @param {string} importedGetName - name that `get` is imported under (if at all)\n */\nfunction fix({\n  fixer,\n  context,\n  node,\n  nodeObject,\n  nodeProperty,\n  nodeDefault,\n  isImported,\n  importedGetName,\n}) {\n  const sourceCode = context.sourceCode;\n\n  const nodeObjectSourceText = sourceCode.getText(nodeObject);\n  const nodePropertySourceText = sourceCode.getText(nodeProperty);\n  const nodeDefaultSourceText = sourceCode.getText(nodeDefault);\n\n  // We convert it to use `this.get('property')` here for safety in case of nested paths.\n  // The `no-get` rule can then convert it to ES5 getters (`this.property`) if safe.\n  // eslint-disable-next-line unicorn/prefer-logical-operator-over-ternary\n  const getName = importedGetName ? importedGetName : 'get';\n  const fixed = isImported\n    ? `(${getName}(${nodeObjectSourceText}, ${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : ${getName}(${nodeObjectSourceText}, ${nodePropertySourceText}))`\n    : `(${nodeObjectSourceText}.get(${nodePropertySourceText}) === undefined ? ${nodeDefaultSourceText} : ${nodeObjectSourceText}.get(${nodePropertySourceText}))`;\n\n  return !isImported || importedGetName\n    ? fixer.replaceText(node, fixed)\n    : [\n        // Need to add import statement for `get`.\n        fixer.insertTextBefore(sourceCode.ast, \"import { get } from '@ember/object';\\n\"),\n        fixer.replaceText(node, fixed),\n      ];\n}\n"
  },
  {
    "path": "lib/rules/no-get.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst utils = require('../utils/utils');\nconst assert = require('assert');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE_GET = \"Use ES5 getters (`this.property`) instead of Ember's `get` function\";\n\nconst ERROR_MESSAGE_GET_PROPERTIES =\n  \"Use `{ prop1: this.prop1, prop2: this.prop2, ... }` instead of Ember's `getProperties` function\";\n\nconst VALID_JS_VARIABLE_NAME_REGEXP = new RegExp('^[a-zA-Z_$][0-9a-zA-Z_$]*$');\nconst VALID_JS_ARRAY_INDEX_REGEXP = new RegExp(/^\\d+$/);\nfunction isValidJSVariableName(str) {\n  return VALID_JS_VARIABLE_NAME_REGEXP.test(str);\n}\nfunction isValidJSArrayIndex(str) {\n  return VALID_JS_ARRAY_INDEX_REGEXP.test(str);\n}\nfunction isValidJSPath(str) {\n  return str.split('.').every((part) => isValidJSVariableName(part) || isValidJSArrayIndex(part));\n}\n\nfunction reportGet({ node, context, path, useAt, useOptionalChaining, objectText, sourceCode }) {\n  const isInLeftSideOfAssignmentExpression = utils.isInLeftSideOfAssignmentExpression(node);\n  context.report({\n    node,\n    message: ERROR_MESSAGE_GET,\n    fix(fixer) {\n      return fixGet({\n        node,\n        fixer,\n        path,\n        useOptionalChaining,\n        useAt,\n        isInLeftSideOfAssignmentExpression,\n        objectText,\n        sourceCode,\n      });\n    },\n  });\n}\n\nfunction fixGet({\n  node,\n  fixer,\n  path,\n  useOptionalChaining,\n  useAt,\n  isInLeftSideOfAssignmentExpression,\n  objectText,\n  sourceCode,\n}) {\n  // Add parenthesis around the object text in case of something like this: get(foo || {}, 'bar')\n  const objectTextSafe = isValidJSPath(objectText) ? objectText : `(${objectText})`;\n\n  const getResultIsChained = node.parent.type === 'MemberExpression' && node.parent.object === node;\n\n  // If the result of get is chained, we can safely autofix nests paths without using optional chaining.\n  // In the left side of an assignment, we can safely autofix nested paths without using optional chaining.\n  const shouldIgnoreOptionalChaining = getResultIsChained || isInLeftSideOfAssignmentExpression;\n\n  if (types.isConditionalExpression(path)) {\n    const newConsequentExpression = convertLiteralTypePath({\n      path: path.consequent.value,\n      useAt,\n      useOptionalChaining,\n      shouldIgnoreOptionalChaining,\n      objectText,\n    });\n    const newAlternateExpression = convertLiteralTypePath({\n      path: path.alternate.value,\n      useAt,\n      useOptionalChaining,\n      shouldIgnoreOptionalChaining,\n      objectText,\n    });\n\n    // this means the overall expression can't be fixed\n    if (newConsequentExpression === null || newAlternateExpression === null) {\n      return null;\n    }\n\n    let replacementText = `${sourceCode.getText(\n      path.test\n    )} ? ${objectTextSafe}${newConsequentExpression} : ${objectTextSafe}${newAlternateExpression}`;\n\n    if (shouldIgnoreOptionalChaining) {\n      replacementText = `(${replacementText})`;\n    }\n\n    return fixer.replaceText(node, replacementText);\n  }\n\n  const replacementPath = convertLiteralTypePath({\n    path,\n    useAt,\n    useOptionalChaining,\n    shouldIgnoreOptionalChaining,\n    objectText,\n  });\n\n  // null means it can't be fixed\n  if (replacementPath === null) {\n    return null;\n  }\n\n  return fixer.replaceText(node, `${objectTextSafe}${replacementPath}`);\n}\n\nfunction reportGetProperties({ context, node, objectText, properties }) {\n  let _properties = properties;\n  if (properties[0].type === 'ArrayExpression') {\n    // When properties are in an array(e.g. getProperties(this.obj, [bar, foo]) ), actual properties are under Array.elements.\n    _properties = properties[0].elements;\n  }\n  const propertyNames = _properties.map((arg) => arg.value);\n\n  context.report({\n    node,\n    message: ERROR_MESSAGE_GET_PROPERTIES,\n    fix(fixer) {\n      return fixGetProperties({\n        fixer,\n        node,\n        objectText,\n        propertyNames,\n      });\n    },\n  });\n}\n\nfunction fixGetProperties({ fixer, node, objectText, propertyNames }) {\n  if (!propertyNames.every((name) => isValidJSVariableName(name))) {\n    // Do not autofix if any property is invalid.\n    return null;\n  }\n\n  if (node.parent.type === 'VariableDeclarator' && node.parent.id.type === 'ObjectPattern') {\n    // When destructuring assignment is in the left side of \"=\".\n    // Example: const { foo, bar } = getProperties(this.obj, \"foo\", \"bar\");\n    // Expectation:\n    // const { foo, bar } = this.obj;\n\n    return fixer.replaceText(node, `${objectText}`);\n  }\n\n  const newProperties = propertyNames.map((name) => `${name}: ${objectText}.${name}`).join(', ');\n  return fixer.replaceText(node, `{ ${newProperties} }`);\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE_GET,\n  ERROR_MESSAGE_GET_PROPERTIES,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: \"require using ES5 getters instead of Ember's `get` / `getProperties` functions\",\n      category: 'Ember Object',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-get.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          ignoreGetProperties: {\n            type: 'boolean',\n            default: false,\n            description: 'Whether the rule should ignore `getProperties`.',\n          },\n          ignoreNestedPaths: {\n            type: 'boolean',\n            default: false,\n            description:\n              \"Whether the rule should ignore `this.get('some.nested.property')` (can't be enabled at the same time as `useOptionalChaining`).\",\n          },\n          useOptionalChaining: {\n            type: 'boolean',\n            default: true,\n            description:\n              \"Whether the rule should use the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) `?.` to autofix nested paths such as `this.get('some.nested.property')` to `this.some?.nested?.property` (when this option is off, these nested paths won't be autofixed at all).\",\n          },\n          catchSafeObjects: {\n            type: 'boolean',\n            default: true,\n            description:\n              \"Whether the rule should catch non-`this` imported usages like `get(foo, 'bar')`.\",\n          },\n          catchUnsafeObjects: {\n            type: 'boolean',\n            default: false,\n            description:\n              \"Whether the rule should catch non-`this` usages like `foo.get('bar')` even though we don't know for sure if `foo` is an Ember object.\",\n          },\n          useAt: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should use `at(-1)` [Array.prototype.at()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at) to replace `lastObject`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n  create(context) {\n    // Options:\n    const ignoreGetProperties = context.options[0] && context.options[0].ignoreGetProperties;\n    const ignoreNestedPaths = context.options[0] && context.options[0].ignoreNestedPaths;\n    const useAt = context.options[0] && context.options[0].useAt;\n    const useOptionalChaining = context.options[0] && context.options[0].useOptionalChaining;\n    const catchSafeObjects = context.options[0] ? context.options[0].catchSafeObjects : true;\n    const catchUnsafeObjects = context.options[0] && context.options[0].catchUnsafeObjects;\n\n    if (ignoreNestedPaths && useOptionalChaining) {\n      assert(\n        false,\n        'Do not enable both the `ignoreNestedPaths` and `useOptionalChaining` options on this rule at the same time.'\n      );\n    }\n\n    const proxyObjects = [];\n    let currentClassWithUnknownPropertyMethod = null;\n\n    let importedGetName;\n    let importedGetPropertiesName;\n\n    const filename = context.filename;\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    // Skip mirage directory\n    if (emberUtils.isMirageConfig(filename)) {\n      return {};\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object') {\n          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');\n          importedGetPropertiesName =\n            importedGetPropertiesName ||\n            getImportIdentifier(node, '@ember/object', 'getProperties');\n        }\n      },\n\n      'CallExpression:exit'(node) {\n        if (proxyObjects.at(-1) === node) {\n          proxyObjects.pop();\n        }\n        if (currentClassWithUnknownPropertyMethod === node) {\n          currentClassWithUnknownPropertyMethod = null;\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        if (proxyObjects.at(-1) === node) {\n          proxyObjects.pop();\n        }\n        if (currentClassWithUnknownPropertyMethod === node) {\n          currentClassWithUnknownPropertyMethod = null;\n        }\n      },\n\n      ClassDeclaration(node) {\n        if (emberUtils.isEmberProxy(context, node)) {\n          proxyObjects.push(node); // Keep track of being inside a proxy object.\n        }\n        if (emberUtils.isEmberObjectImplementingUnknownProperty(node, scopeManager)) {\n          currentClassWithUnknownPropertyMethod = node; // Keep track of being inside an object implementing `unknownProperty`.\n        }\n      },\n\n      // eslint-disable-next-line complexity\n      CallExpression(node) {\n        // **************************\n        // Check for situations which the rule should ignore.\n        // **************************\n\n        if (emberUtils.isEmberProxy(context, node)) {\n          proxyObjects.push(node); // Keep track of being inside a proxy object.\n        }\n        if (emberUtils.isEmberObjectImplementingUnknownProperty(node, scopeManager)) {\n          currentClassWithUnknownPropertyMethod = node;\n        }\n        if (proxyObjects.at(-1) || currentClassWithUnknownPropertyMethod) {\n          // Proxy objects and objects implementing `unknownProperty()`\n          // still require using `get()`, so ignore any code inside them.\n          return;\n        }\n\n        // **************************\n        // get\n        // **************************\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          (types.isThisExpression(node.callee.object) || catchUnsafeObjects) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'get' &&\n          node.arguments.length === 1 &&\n          types.isStringLiteral(node.arguments[0]) &&\n          (!node.arguments[0].value.includes('.') || !ignoreNestedPaths)\n        ) {\n          // Example: this.get('foo');\n          const sourceCode = context.sourceCode;\n          reportGet({\n            node,\n            context,\n            path: node.arguments[0].value,\n            isImportedGet: false,\n            useOptionalChaining,\n            useAt,\n            objectText: sourceCode.getText(node.callee.object),\n          });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetName &&\n          node.arguments.length === 2 &&\n          (types.isThisExpression(node.arguments[0]) || catchSafeObjects) &&\n          types.isStringLiteral(node.arguments[1]) &&\n          (!node.arguments[1].value.includes('.') || !ignoreNestedPaths)\n        ) {\n          // Example: get(this, 'foo');\n          const sourceCode = context.sourceCode;\n          reportGet({\n            node,\n            context,\n            path: node.arguments[1].value,\n            isImportedGet: true,\n            useOptionalChaining,\n            useAt,\n            objectText: sourceCode.getText(node.arguments[0]),\n          });\n        }\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          (types.isThisExpression(node.callee.object) || catchUnsafeObjects) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'get' &&\n          node.arguments.length === 1 &&\n          node.arguments[0].type === 'Literal' &&\n          typeof node.arguments[0].value === 'number'\n        ) {\n          // Example: this.get(5);\n          const sourceCode = context.sourceCode;\n          reportGet({\n            node,\n            context,\n            path: node.arguments[0].value,\n            isImportedGet: false,\n            objectText: sourceCode.getText(node.callee.object),\n          });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetName &&\n          node.arguments.length === 2 &&\n          (types.isThisExpression(node.arguments[0]) || catchSafeObjects) &&\n          node.arguments[1].type === 'Literal' &&\n          typeof node.arguments[1].value === 'number'\n        ) {\n          // Example: get(this, 5);\n          const sourceCode = context.sourceCode;\n          reportGet({\n            node,\n            context,\n            path: node.arguments[1].value,\n            isImportedGet: true,\n            objectText: sourceCode.getText(node.arguments[0]),\n          });\n        }\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          (types.isThisExpression(node.callee.object) || catchUnsafeObjects) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'get' &&\n          node.arguments.length === 1 &&\n          types.isConditionalExpression(node.arguments[0]) &&\n          types.isLiteral(node.arguments[0].consequent) &&\n          types.isLiteral(node.arguments[0].alternate)\n        ) {\n          // Example: this.get(foo ? 'bar' : 'baz');\n          const sourceCode = context.sourceCode;\n          reportGet({\n            node,\n            context,\n            path: node.arguments[0],\n            isImportedGet: false,\n            objectText: sourceCode.getText(node.callee.object),\n            useOptionalChaining,\n            sourceCode,\n          });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetName &&\n          node.arguments.length === 2 &&\n          (types.isThisExpression(node.arguments[0]) || catchSafeObjects) &&\n          types.isConditionalExpression(node.arguments[1]) &&\n          types.isLiteral(node.arguments[1].consequent) &&\n          types.isLiteral(node.arguments[1].alternate)\n        ) {\n          // Example: get(foo, bar ? 'baz' : 'biz');\n          const sourceCode = context.sourceCode;\n          reportGet({\n            node,\n            context,\n            path: node.arguments[1],\n            isImportedGet: true,\n            objectText: sourceCode.getText(node.arguments[0]),\n            useOptionalChaining,\n            sourceCode,\n          });\n        }\n\n        // **************************\n        // getProperties\n        // **************************\n\n        if (ignoreGetProperties) {\n          return;\n        }\n\n        if (\n          types.isMemberExpression(node.callee) &&\n          (types.isThisExpression(node.callee.object) || catchUnsafeObjects) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'getProperties' &&\n          validateGetPropertiesArguments(node.arguments, ignoreNestedPaths)\n        ) {\n          // Example: this.getProperties('foo', 'bar');\n          const objectText = context.sourceCode.getText(node.callee.object);\n          const properties = node.arguments;\n          reportGetProperties({ context, node, objectText, properties });\n        }\n\n        if (\n          types.isIdentifier(node.callee) &&\n          node.callee.name === importedGetPropertiesName &&\n          (types.isThisExpression(node.arguments[0]) || catchSafeObjects) &&\n          validateGetPropertiesArguments(node.arguments.slice(1), ignoreNestedPaths)\n        ) {\n          // Example: getProperties(this, 'foo', 'bar');\n          const objectText = context.sourceCode.getText(node.arguments[0]);\n          const properties = node.arguments.slice(1);\n          reportGetProperties({\n            context,\n            node,\n            objectText,\n            properties,\n          });\n        }\n      },\n    };\n  },\n};\n\nfunction validateGetPropertiesArguments(args, ignoreNestedPaths) {\n  if (args.length === 1 && types.isArrayExpression(args[0])) {\n    return validateGetPropertiesArguments(args[0].elements, ignoreNestedPaths);\n  }\n  // We can only handle string arguments without nested property paths.\n  return args.every(\n    (argument) =>\n      types.isStringLiteral(argument) && (!argument.value.includes('.') || !ignoreNestedPaths)\n  );\n}\n\nfunction convertLiteralTypePath({\n  path,\n  useAt,\n  useOptionalChaining,\n  shouldIgnoreOptionalChaining,\n  objectText,\n}) {\n  if (typeof path === 'number') {\n    return `[${path}]`;\n  }\n\n  if (path.includes('.') && !useOptionalChaining && !shouldIgnoreOptionalChaining) {\n    // Not safe to autofix nested properties because some properties in the path might be null or undefined.\n    return null;\n  }\n\n  if (!isValidJSPath(path)) {\n    // Do not autofix since the path would not be a valid JS path.\n    return null;\n  }\n\n  if (path.match(/lastObject/g)?.length > 1 && !useAt) {\n    // Do not autofix when multiple `lastObject` are chained, and use `at(-1)` is not allowed.\n    return null;\n  }\n\n  let replacementPath = shouldIgnoreOptionalChaining ? path : path.replaceAll('.', '?.');\n\n  // Replace any array element access (foo.1 => foo[1] or foo?.[1]).\n  replacementPath = replacementPath\n    .replaceAll(/\\.(\\d+)/g, shouldIgnoreOptionalChaining ? '[$1]' : '.[$1]') // Usages in middle of path.\n    .replace(/^(\\d+)\\??\\./, shouldIgnoreOptionalChaining ? '[$1].' : '[$1]?.') // Usage at beginning of path.\n    .replace(/^(\\d+)$/, '[$1]'); // Usage as entire string.\n\n  // Replace any array element access using `firstObject` and `lastObject` (foo.firstObject => foo[0] or foo?.[0]).\n  replacementPath = replacementPath\n    .replaceAll('.firstObject', shouldIgnoreOptionalChaining ? '[0]' : '.[0]') // When `firstObject` is used in the middle of the path. e.g. foo.firstObject\n    .replace(/^firstObject\\??\\./, shouldIgnoreOptionalChaining ? '[0].' : '[0]?.') // When `firstObject` is used at the beginning of the path. e.g. firstObject.bar\n    .replace(/^firstObject$/, '[0]'); // When `firstObject` is used as the entire path.\n\n  // eslint-disable-next-line unicorn/prefer-ternary\n  if (useAt) {\n    replacementPath = replacementPath.replaceAll('lastObject', 'at(-1)');\n  } else {\n    replacementPath = replacementPath\n      .replace(\n        /\\??\\.lastObject/, // When `lastObject` is used in the middle of the path. e.g. foo.lastObject\n        (_, offset) =>\n          `${shouldIgnoreOptionalChaining ? '' : '?.'}[${objectText}.${replacementPath.slice(\n            0,\n            offset\n          )}.length - 1]`\n      )\n      .replace(\n        /^lastObject\\??\\./, // When `lastObject` is used at the beginning of the path. e.g. lastObject.bar\n        `[${objectText}.length - 1]${shouldIgnoreOptionalChaining ? '.' : '?.'}`\n      )\n      .replace(/^lastObject$/, `[${objectText}.length - 1]`); // When `lastObject` is used as the entire path.\n  }\n\n  const objectPathSeparator = replacementPath.startsWith('[') ? '' : '.';\n\n  return `${objectPathSeparator}${replacementPath}`;\n}\n"
  },
  {
    "path": "lib/rules/no-global-jquery.js",
    "content": "'use strict';\n\nconst { ReferenceTracker } = require('eslint-utils');\nconst { globalMap } = require('../utils/jquery');\n\nconst ERROR_MESSAGE = 'Do not use global `$` or `jQuery`';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of global jQuery object',\n      category: 'jQuery',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-global-jquery.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      Program(node) {\n        const sourceCode = context.sourceCode;\n        const scope = sourceCode.getScope(node);\n\n        const tracker = new ReferenceTracker(scope);\n\n        for (const { node } of tracker.iterateGlobalReferences(globalMap)) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-html-safe.js",
    "content": "'use strict';\n\nconst ERROR_MESSAGE = 'Do not use `htmlSafe`.';\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow the use of `htmlSafe`',\n      category: 'Miscellaneous',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-html-safe.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedHtmlSafeName;\n\n    return {\n      ImportDeclaration(node) {\n        const importSource = node.source.value;\n\n        if (importSource === '@ember/string') {\n          importedHtmlSafeName =\n            importedHtmlSafeName || getImportIdentifier(node, '@ember/string', 'htmlSafe');\n        } else if (importSource === '@ember/template') {\n          importedHtmlSafeName =\n            importedHtmlSafeName || getImportIdentifier(node, '@ember/template', 'htmlSafe');\n        }\n      },\n\n      CallExpression(node) {\n        if (node.callee.type === 'Identifier' && node.callee.name === importedHtmlSafeName) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-implicit-injections.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\nconst Stack = require('../utils/stack');\nconst types = require('../utils/types');\nconst camelCase = require('lodash.camelcase');\n\nconst defaultServiceConfig = { service: 'store', moduleNames: ['Route', 'Controller'] };\nconst MODULE_TYPES = [\n  'Component',\n  'GlimmerComponent',\n  'Controller',\n  'Mixin',\n  'Route',\n  'Service',\n  'ArrayProxy',\n  'ObjectProxy',\n  'EmberObject',\n  'Helper',\n];\n\n// ----- -------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nfunction fixService(fixer, currentClass, serviceInjectImportName, failedConfig) {\n  const serviceInjectPath = failedConfig.service;\n\n  if (currentClass.node.type === 'CallExpression') {\n    const lastArg = currentClass.node.arguments.at(-1);\n    if (!lastArg || lastArg.type !== 'ObjectExpression' || lastArg.properties.length === 0) {\n      return null;\n    }\n    return fixer.insertTextBefore(\n      lastArg.properties[0],\n      `${failedConfig.propertyName}: ${serviceInjectImportName}('${serviceInjectPath}'),\\n`\n    );\n  }\n\n  if (currentClass.node.body.body.length === 0) {\n    return null;\n  }\n  return fixer.insertTextBefore(\n    currentClass.node.body.body[0],\n    `@${serviceInjectImportName}('${serviceInjectPath}') ${failedConfig.propertyName};\\n`\n  );\n}\n\nfunction normalizeConfiguration(denyList) {\n  return denyList.map((config) => ({\n    service: config.service,\n    propertyName: config.propertyName ?? camelCase(config.service),\n    moduleNames: config.moduleNames ?? MODULE_TYPES,\n  }));\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of implicit service injections',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-implicit-injections.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        required: ['denyList'],\n        properties: {\n          denyList: {\n            minItems: 1,\n            type: 'array',\n            items: {\n              type: 'object',\n              default: [defaultServiceConfig],\n              required: ['service'],\n              properties: {\n                service: {\n                  type: 'string',\n                  minLength: 1,\n                },\n                propertyName: {\n                  type: 'string',\n                  minLength: 1,\n                },\n                moduleNames: {\n                  type: 'array',\n                  items: {\n                    enum: MODULE_TYPES,\n                  },\n                },\n              },\n              additionalProperties: false,\n            },\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      main: 'Do not rely on implicit service injections for the \"{{serviceName}}\" service. Implicit service injections were deprecated in Ember 3.26 and will not work in 4.0.',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {\n      denyList: [defaultServiceConfig],\n    };\n    const denyList = options.denyList || [defaultServiceConfig];\n\n    for (const config of denyList) {\n      assert(\n        emberUtils.convertServiceNameToKebabCase(config.service) === config.service,\n        'Service name should be passed in kebab-case (all lower case)'\n      );\n\n      assert(\n        !config.service.includes('/') || config.propertyName,\n        'Nested services must declare a property name'\n      );\n    }\n\n    // State being tracked for this file.\n    let serviceInjectImportName = undefined;\n    const normalizedConfiguration = normalizeConfiguration(denyList);\n\n    // State being tracked for the current class we're inside.\n    const classStack = new Stack();\n\n    // Array of posible types that could declare existing properties on native or legacy modules\n    const propertyDefintionTypes = new Set([\n      'Property',\n      'ClassProperty',\n      'PropertyDefinition',\n      'MethodDefinition',\n    ]);\n\n    function onModuleFound(node) {\n      // Get the name of services for the current module type\n      let configToCheckFor = normalizedConfiguration.filter((serviceConfig) => {\n        return (\n          serviceConfig.moduleNames === undefined ||\n          serviceConfig.moduleNames.some((moduleName) =>\n            emberUtils.isEmberCoreModule(context, node, moduleName)\n          )\n        );\n      });\n\n      const modulePropertyDeclarations =\n        node.type === 'CallExpression' ? node.arguments.at(-1).properties : node.body.body;\n\n      // Get Services that don't have properties/service injections declared\n      configToCheckFor = modulePropertyDeclarations.reduce((accum, n) => {\n        if (propertyDefintionTypes.has(n.type)) {\n          return accum.filter((config) => !(config.propertyName === n.key.name));\n        }\n        return accum;\n      }, configToCheckFor);\n\n      classStack.push({\n        node,\n        isEmberModule: true,\n        configToCheckFor,\n      });\n    }\n\n    function onClassEnter(node) {\n      if (emberUtils.isAnyEmberCoreModule(context, node)) {\n        onModuleFound(node);\n      } else {\n        classStack.push({\n          node,\n          isEmberModule: false,\n        });\n      }\n    }\n\n    function onClassExit(node) {\n      // Leaving current (native) class.\n      if (classStack.size() > 0 && classStack.peek().node === node) {\n        classStack.pop();\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/service') {\n          serviceInjectImportName =\n            serviceInjectImportName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n\n      ClassDeclaration: onClassEnter,\n      ClassExpression: onClassEnter,\n      CallExpression(node) {\n        if (emberUtils.isAnyEmberCoreModule(context, node) && emberUtils.isExtendObject(node)) {\n          const lastExtendArg = node.arguments.at(-1);\n\n          if (lastExtendArg && lastExtendArg.type === 'ObjectExpression') {\n            onModuleFound(node);\n          }\n        }\n      },\n\n      'ClassDeclaration:exit': onClassExit,\n      'ClassExpression:exit': onClassExit,\n      'CallExpression:exit': onClassExit,\n\n      MemberExpression(node) {\n        const currentClass = classStack.peek();\n\n        if (!currentClass || !currentClass.isEmberModule) {\n          return;\n        }\n\n        if (types.isThisExpression(node.object) && types.isIdentifier(node.property)) {\n          const failedConfig = currentClass.configToCheckFor.find(\n            (s) => s.propertyName === node.property.name\n          );\n\n          if (failedConfig) {\n            context.report({\n              node,\n              messageId: 'main',\n              data: {\n                serviceName: failedConfig.service,\n              },\n              fix(fixer) {\n                const sourceCode = context.sourceCode;\n\n                // service inject is already declared\n                if (serviceInjectImportName) {\n                  return fixService(fixer, currentClass, serviceInjectImportName, failedConfig);\n                }\n\n                return [\n                  fixer.insertTextBefore(\n                    sourceCode.ast,\n                    \"import { inject as service } from '@ember/service';\\n\"\n                  ),\n                  fixService(fixer, currentClass, 'service', failedConfig),\n                ];\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-implicit-service-injection-argument.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = \"Don't omit the argument for the injected service name.\";\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow omitting the injected service name argument',\n      category: 'Services',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-implicit-service-injection-argument.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedInjectName;\n    let importedEmberName;\n\n    // Handle either ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).\n    function visitClassPropertyOrPropertyDefinition(node) {\n      // Handle native classes.\n\n      if (\n        !emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName) ||\n        node.decorators.length !== 1\n      ) {\n        return;\n      }\n\n      if (\n        types.isCallExpression(node.decorators[0].expression) &&\n        node.decorators[0].expression.arguments.length > 0\n      ) {\n        // Already has the service name argument.\n        return;\n      }\n\n      context.report({\n        node: node.decorators[0].expression,\n        message: ERROR_MESSAGE,\n        fix(fixer) {\n          const sourceCode = context.sourceCode;\n\n          // Ideally, we want to match the service's filename, and kebab-case filenames are most common.\n          const serviceName = emberUtils.convertServiceNameToKebabCase(\n            node.key.name || node.key.value\n          );\n\n          return node.decorators[0].expression.type === 'CallExpression'\n            ? // Add after parenthesis.\n              fixer.insertTextAfter(\n                sourceCode.getTokenAfter(node.decorators[0].expression.callee),\n                `'${serviceName}'`\n              )\n            : // No parenthesis yet so we need to add them.\n              fixer.insertTextAfter(node.decorators[0].expression, `('${serviceName}')`);\n        },\n      });\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n      Property(node) {\n        // Classic classes.\n\n        if (\n          !emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName) ||\n          node.value.arguments.length > 0\n        ) {\n          // Already has the service name argument.\n          return;\n        }\n\n        if (node.value.arguments.length === 0) {\n          context.report({\n            node: node.value,\n            message: ERROR_MESSAGE,\n            fix(fixer) {\n              const sourceCode = context.sourceCode;\n\n              // Ideally, we want to match the service's filename, and kebab-case filenames are most common.\n              const serviceName = emberUtils.convertServiceNameToKebabCase(\n                node.key.name || node.key.value\n              );\n\n              return fixer.insertTextAfter(\n                sourceCode.getTokenAfter(node.value.callee),\n                `'${serviceName}'`\n              );\n            },\n          });\n        }\n      },\n      ClassProperty: visitClassPropertyOrPropertyDefinition,\n      PropertyDefinition: visitClassPropertyOrPropertyDefinition,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-incorrect-calls-with-inline-anonymous-functions.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\n\nconst ERROR_MESSAGE =\n  'Do not use anonymous functions as arguments to `debounce`, `once`, and `scheduleOnce`.';\n\nfunction isMemberExpressionOnRun(node) {\n  return types.isMemberExpression(node.callee) && node.callee.object.name === 'run';\n}\n\nconst functionRules = [\n  { importPath: '@ember/runloop', importName: 'debounce' },\n  { importPath: '@ember/runloop', importName: 'once' },\n  { importPath: '@ember/runloop', importName: 'scheduleOnce' },\n];\n\nconst allDedupingRunMethodNames = new Set(functionRules.map((rule) => rule.importName));\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'disallow inline anonymous functions as arguments to `debounce`, `once`, and `scheduleOnce`',\n      category: 'Miscellaneous',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md',\n    },\n    schema: [],\n  },\n\n  create(context) {\n    function checkArgumentsForInlineFunction(node) {\n      for (const [index, argument] of node.arguments.entries()) {\n        if (types.isAnyFunctionExpression(argument)) {\n          context.report({ node: node.arguments[index], message: ERROR_MESSAGE });\n        }\n      }\n    }\n    function checkFunctionRule(functionRule, node) {\n      if (functionRule.importName === node.callee.name) {\n        checkArgumentsForInlineFunction(node);\n      }\n    }\n\n    let importedRun = false;\n    const inactiveFunctionRules = new Set(functionRules);\n    const activeFunctionRules = new Set();\n\n    return {\n      ImportDeclaration(node) {\n        const importPath = node.source.value;\n        const namedImports = new Set(\n          node.specifiers\n            .filter((specifier) => specifier.imported)\n            .map((specifier) => {\n              return specifier.imported.name;\n            })\n        );\n\n        for (const functionRule of inactiveFunctionRules) {\n          if (functionRule.importPath === importPath && namedImports.has(functionRule.importName)) {\n            inactiveFunctionRules.delete(functionRule);\n            activeFunctionRules.add(functionRule);\n          }\n        }\n\n        if (node.source.value === '@ember/runloop' && namedImports.has('run')) {\n          importedRun = true;\n        }\n      },\n      CallExpression(node) {\n        for (const functionRule of activeFunctionRules) {\n          checkFunctionRule(functionRule, node);\n        }\n\n        if (importedRun && isMemberExpressionOnRun(node)) {\n          if (allDedupingRunMethodNames.has(node.callee.property.name)) {\n            checkArgumentsForInlineFunction(node);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-incorrect-computed-macros.js",
    "content": "'use strict';\n\nconst { getImportIdentifier } = require('../utils/import');\nconst types = require('../utils/types');\n\nconst ERROR_MESSAGE_AND_OR = 'Computed property macro should be used with 2+ arguments';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow incorrect usage of computed property macros',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-incorrect-computed-macros.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE_AND_OR,\n\n  create(context) {\n    let importNameAnd = undefined;\n    let importNameOr = undefined;\n    let importNameReadOnly = undefined;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object/computed') {\n          // Gather the identifiers that these macros are imported under.\n          importNameAnd =\n            importNameAnd || getImportIdentifier(node, '@ember/object/computed', 'and');\n          importNameOr = importNameOr || getImportIdentifier(node, '@ember/object/computed', 'or');\n          importNameReadOnly =\n            importNameReadOnly || getImportIdentifier(node, '@ember/object/computed', 'readOnly');\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          types.isIdentifier(node.callee) &&\n          [importNameAnd, importNameOr].includes(node.callee.name)\n        ) {\n          if (\n            node.arguments.length === 1 &&\n            types.isStringLiteral(node.arguments[0]) &&\n            !node.arguments[0].value.includes('{')\n          ) {\n            context.report({\n              node: node.callee,\n              message: ERROR_MESSAGE_AND_OR,\n              fix(fixer) {\n                if (importNameReadOnly) {\n                  return fixer.replaceText(node.callee, importNameReadOnly);\n                } else {\n                  const sourceCode = context.sourceCode;\n                  return [\n                    fixer.insertTextBefore(\n                      sourceCode.ast,\n                      \"import { readOnly } from '@ember/object/computed';\\n\"\n                    ),\n                    fixer.replaceText(node.callee, 'readOnly'),\n                  ];\n                }\n              },\n            });\n          } else if (node.arguments.length === 0) {\n            context.report({ node, message: ERROR_MESSAGE_AND_OR });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-invalid-debug-function-arguments.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE =\n  'This Ember debug function has its arguments passed in the wrong order. `String description` should come before `Boolean condition`.';\n\nconst DEBUG_FUNCTIONS = ['assert', 'deprecate', 'warn'];\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        \"disallow usages of Ember's `assert()` / `warn()` / `deprecate()` functions that have the arguments passed in the wrong order.\",\n      category: 'Miscellaneous',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-invalid-debug-function-arguments.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n  DEBUG_FUNCTIONS,\n\n  create(context) {\n    let importedIdentifiers = [];\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value !== '@ember/debug') {\n          return;\n        }\n\n        // Gather the identifiers that these functions are imported under.\n        importedIdentifiers = DEBUG_FUNCTIONS.map((fn) =>\n          getImportIdentifier(node, '@ember/debug', fn)\n        );\n      },\n\n      CallExpression(node) {\n        if (isDebugFunctionWithReversedArgs(node, importedIdentifiers)) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n\nfunction isDebugFunctionWithReversedArgs(node, importedIdentifiers) {\n  return (\n    isDebugFunction(node, importedIdentifiers) &&\n    node.arguments.length >= 2 &&\n    !types.isString(node.arguments[0]) &&\n    types.isString(node.arguments[1])\n  );\n}\n\nfunction isDebugFunction(node, importedIdentifiers) {\n  return getDebugFunction(node, importedIdentifiers) !== undefined;\n}\n\nfunction getDebugFunction(node, importedIdentifiers) {\n  const isEmberDebugFunctionCall = DEBUG_FUNCTIONS.find(\n    (debugFunction) =>\n      types.isMemberExpression(node.callee) &&\n      types.isIdentifier(node.callee.object) &&\n      node.callee.object.name === 'Ember' &&\n      types.isIdentifier(node.callee.property) &&\n      node.callee.property.name === debugFunction\n  );\n\n  const isImportedDebugFunctionCall = importedIdentifiers.find(\n    (debugFunction) => types.isIdentifier(node.callee) && node.callee.name === debugFunction\n  );\n\n  return isEmberDebugFunctionCall || isImportedDebugFunctionCall;\n}\n"
  },
  {
    "path": "lib/rules/no-invalid-dependent-keys.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst ember = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// General rule -  Dependent keys used for computed properties have to be valid.\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE_UNBALANCED_BRACES = 'Found unbalanced braces in dependent key';\nconst ERROR_MESSAGE_UNNECESSARY_BRACES = 'Found unnecessary braces in dependent key';\nconst ERROR_MESSAGE_TERMINAL_AT_EACH = 'Found terminal `@each`, use `[]` instead';\nconst ERROR_MESSAGE_MIDDLE_BRACKETS = '`[]` should only be used at the end of the dependent key';\nconst ERROR_MESSAGE_LEADING_TRAILING_PERIOD = 'Found leading or trailing period in dependent key';\nconst ERROR_MESSAGE_INVALID_CHARACTER = 'Found invalid character in dependent key';\n\nfunction hasUnbalancedBraces(str) {\n  const foundBraces = str.match(/[{}]/g) || [];\n  const openBraces = foundBraces.filter((c) => c === '{').length;\n  const closeBraces = foundBraces.filter((c) => c === '}').length;\n  return openBraces !== closeBraces;\n}\n\nconst REGEXP_UNNECESSARY_BRACES = /{([^,.]+)}/g;\nfunction hasUnnecessaryBraces(str) {\n  return str.match(REGEXP_UNNECESSARY_BRACES);\n}\n\nfunction hasTerminalAtEach(str) {\n  return str.endsWith('.@each');\n}\n\nfunction hasMiddleBrackets(str) {\n  return str.includes('[].');\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow invalid dependent keys in computed properties',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-invalid-dependent-keys.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE_UNBALANCED_BRACES,\n  ERROR_MESSAGE_UNNECESSARY_BRACES,\n  ERROR_MESSAGE_TERMINAL_AT_EACH,\n  ERROR_MESSAGE_MIDDLE_BRACKETS,\n  ERROR_MESSAGE_LEADING_TRAILING_PERIOD,\n  ERROR_MESSAGE_INVALID_CHARACTER,\n\n  create(context) {\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      CallExpression(node) {\n        if (!ember.isComputedProp(node, importedEmberName, importedComputedName)) {\n          return;\n        }\n\n        const stringArgs = node.arguments.filter(\n          (arg) => types.isLiteral(arg) && typeof arg.value === 'string'\n        );\n\n        const sourceCode = context.sourceCode;\n\n        for (const node of stringArgs) {\n          if (hasTerminalAtEach(node.value)) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_TERMINAL_AT_EACH,\n              fix(fixer) {\n                const nodeText = sourceCode.getText(node);\n                return fixer.replaceText(node, nodeText.replace('.@each', '.[]'));\n              },\n            });\n          }\n\n          if (hasUnbalancedBraces(node.value)) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_UNBALANCED_BRACES,\n            });\n          }\n\n          if (hasUnnecessaryBraces(node.value)) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_UNNECESSARY_BRACES,\n              fix(fixer) {\n                const nodeText = sourceCode.getText(node);\n                return fixer.replaceText(\n                  node,\n                  nodeText.replaceAll(REGEXP_UNNECESSARY_BRACES, '$1')\n                );\n              },\n            });\n          }\n\n          if (hasMiddleBrackets(node.value)) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_MIDDLE_BRACKETS,\n              fix(fixer) {\n                const nodeText = sourceCode.getText(node);\n                return fixer.replaceText(node, nodeText.replace('[].', '@each.'));\n              },\n            });\n          }\n\n          if (node.value.startsWith('.')) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,\n              fix(fixer) {\n                const nodeText = sourceCode.getText(node);\n                return fixer.replaceText(node, nodeText.replace('.', '')); // Remove first period.\n              },\n            });\n          }\n\n          if (node.value.endsWith('.')) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,\n              fix(fixer) {\n                const nodeText = sourceCode.getText(node);\n                return fixer.replaceText(node, removeLastOccurrenceOf(nodeText, '.'));\n              },\n            });\n          }\n\n          if (node.value.includes(' ')) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE_INVALID_CHARACTER,\n              fix(fixer) {\n                const nodeText = sourceCode.getText(node);\n                return fixer.replaceText(node, nodeText.replaceAll(' ', '')); // Remove all spaces.\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n\nfunction removeLastOccurrenceOf(strToSearch, strToRemove) {\n  const posToRemove = strToSearch.lastIndexOf(strToRemove);\n  return strToSearch.slice(0, posToRemove) + strToSearch.slice(posToRemove + 1);\n}\n"
  },
  {
    "path": "lib/rules/no-invalid-test-waiters.js",
    "content": "'use strict';\n\nconst { getImportIdentifier } = require('../utils/import');\nconst utils = require('../utils/utils');\n\nconst MODULE_SCOPE_ERROR_MESSAGE = 'The buildWaiter function should be invoked in module scope.';\nconst DIRECT_ASSIGNMENT_ERROR_MESSAGE =\n  'The result of the `buildWaiter` function must be a direct assignment to a module scoped variable';\n\nfunction isInModuleScope(node) {\n  const ancestorVariableDeclaration = utils.getAncestor(\n    node,\n    (ancestor) => ancestor.type === 'VariableDeclaration'\n  );\n\n  return ancestorVariableDeclaration.parent.type === 'Program';\n}\n\nfunction isDirectVariableDeclaration(node) {\n  return node.parent.type === 'VariableDeclarator';\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow incorrect usage of test waiter APIs',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-invalid-test-waiters.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  MODULE_SCOPE_ERROR_MESSAGE,\n  DIRECT_ASSIGNMENT_ERROR_MESSAGE,\n\n  create(context) {\n    let buildWaiter;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember-test-waiters') {\n          buildWaiter =\n            buildWaiter || getImportIdentifier(node, 'ember-test-waiters', 'buildWaiter');\n        }\n      },\n\n      CallExpression(node) {\n        if (node.callee.type !== 'Identifier' || node.callee.name !== buildWaiter) {\n          return;\n        }\n\n        if (!isDirectVariableDeclaration(node)) {\n          context.report({\n            node,\n            message: DIRECT_ASSIGNMENT_ERROR_MESSAGE,\n          });\n        } else if (!isInModuleScope(node)) {\n          context.report({\n            node,\n            message: MODULE_SCOPE_ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-jquery.js",
    "content": "'use strict';\n\nconst { ReferenceTracker } = require('eslint-utils');\nconst { isCallExpression } = require('../utils/types');\nconst { globalMap, esmMap } = require('../utils/jquery');\n\n//------------------------------------------------------------------------------\n// General rule -  Disallow usage of jQuery\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = 'Do not use jQuery';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow any usage of jQuery',\n      category: 'jQuery',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-jquery.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      /**\n       * Report member expressions of jQuery\n       *\n       * eg; this.$.post() or this.$(body).attr()\n       */\n      'MemberExpression[property.name=\"$\"][object.type=\"ThisExpression\"]'(node) {\n        if (isCallExpression(node.parent)) {\n          report(node.parent);\n        } else {\n          report(node);\n        }\n      },\n\n      /**\n       * Report references of jQuery\n       */\n      Program(node) {\n        const sourceCode = context.sourceCode;\n        const scope = sourceCode.getScope(node);\n\n        const tracker = new ReferenceTracker(scope);\n\n        /**\n         * Global references\n         *\n         * eg; $(body) and $.post()\n         */\n        for (const { node } of tracker.iterateGlobalReferences(globalMap)) {\n          report(node);\n        }\n\n        /**\n         * ESM references\n         *   import $ from 'jquery'\n         *   import { $ as jq } from 'ember'\n         *\n         * eg;\n         *   $(body) and jq.post()\n         */\n        for (const { node } of tracker.iterateEsmReferences(esmMap)) {\n          report(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-legacy-test-waiters.js",
    "content": "'use strict';\nconst { getImportIdentifier } = require('../utils/import');\n\nconst LEGACY_TEST_WAITER_FUNCTIONS = ['registerWaiter', 'unregisterWaiter'];\nconst ERROR_MESSAGE =\n  'The use of the legacy test waiters API is not allowed. Please use the new ember-test-waiters (https://github.com/emberjs/ember-test-waiters) APIs instead.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow the use of the legacy test waiter APIs',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-legacy-test-waiters.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let testWaitersIdentifiers = [];\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value !== '@ember/test') {\n          return;\n        }\n\n        testWaitersIdentifiers = LEGACY_TEST_WAITER_FUNCTIONS.map((fn) =>\n          getImportIdentifier(node, '@ember/test', fn)\n        );\n      },\n\n      CallExpression(node) {\n        if (\n          node.callee.type === 'Identifier' &&\n          testWaitersIdentifiers.includes(node.callee.name)\n        ) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-mixins.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// General rule - Don't use mixins\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = \"Don't use a mixin\";\nconst mixinPathRegex = /\\/mixins\\//;\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow the usage of mixins',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-mixins.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      ImportDeclaration(node) {\n        const importPath = node.source.value;\n        if (mixinPathRegex.test(importPath)) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-modifier-argument-destructuring.js",
    "content": "'use strict';\n\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE =\n  'Do not destructure the positional or named arguments of a modifier. Instead, access them by index or property to avoid eagerly consuming tracked data.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'disallow destructuring of modifier arguments to avoid consuming tracked data too early',\n      category: 'Ember Object',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-modifier-argument-destructuring.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      main: ERROR_MESSAGE,\n    },\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let modifierImportedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember-modifier') {\n          modifierImportedName ??= getImportIdentifier(node, 'ember-modifier', 'modifier');\n        }\n      },\n\n      CallExpression(node) {\n        if (!modifierImportedName) {\n          return;\n        }\n\n        if (node.callee.type !== 'Identifier' || node.callee.name !== modifierImportedName) {\n          return;\n        }\n\n        const callback = node.arguments[0];\n        if (!callback) {\n          return;\n        }\n\n        // Support arrow functions and function expressions\n        if (callback.type !== 'ArrowFunctionExpression' && callback.type !== 'FunctionExpression') {\n          return;\n        }\n\n        // Check 2nd parameter (positional args) - index 1\n        const positionalParam = callback.params[1];\n        if (positionalParam && positionalParam.type === 'ArrayPattern') {\n          context.report({\n            node: positionalParam,\n            messageId: 'main',\n          });\n        }\n\n        // Check 3rd parameter (named args) - index 2\n        const namedParam = callback.params[2];\n        if (namedParam && namedParam.type === 'ObjectPattern') {\n          context.report({\n            node: namedParam,\n            messageId: 'main',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-new-mixins.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\n\n//------------------------------------------------------------------------------\n// General rule - Don't create new mixins\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = \"Don't create new mixins\";\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow the creation of new mixins',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-new-mixins.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      CallExpression(node) {\n        if (ember.isEmberMixin(context, node)) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n\n      ClassDeclaration(node) {\n        if (ember.isEmberMixin(context, node)) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-noop-setup-on-error-in-before.js",
    "content": "'use strict';\n\nconst { getImportIdentifier } = require('../utils/import');\nconst types = require('../utils/types');\n\n//------------------------------------------------------------------------------\n// General rule - Disallows no-op `setupOnError` in `before` or `beforeEach`.\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallows using no-op setupOnerror in `before` or `beforeEach`',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-noop-setup-on-error-in-before.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      main: 'Using no-op setupOnerror in `before` or `beforeEach` is not allowed',\n    },\n  },\n\n  create(context) {\n    let importedSetupOnerrorName, importedModuleName;\n    let isInModule = false;\n    let isInBeforeEachHook = false;\n    let isInBeforeHook = false;\n    let hooksName;\n    const sourceCode = context.sourceCode;\n\n    function reportErrorForNodeIfInBefore(node) {\n      const isInBeforeOrBeforeEach =\n        (isInBeforeEachHook || isInBeforeHook) &&\n        types.isIdentifier(node.callee) &&\n        node.callee.name === importedSetupOnerrorName;\n\n      const callback = node.arguments[0];\n      const isFunction =\n        types.isArrowFunctionExpression(callback) ||\n        types.isFunctionDeclaration(callback) ||\n        types.isFunctionExpression(callback);\n\n      const isNoop =\n        callback &&\n        isFunction &&\n        callback.body &&\n        callback.body.type === 'BlockStatement' &&\n        callback.body.body.length === 0;\n\n      if (isInBeforeOrBeforeEach && isNoop) {\n        context.report({\n          node,\n          messageId: 'main',\n          *fix(fixer) {\n            yield fixer.remove(node);\n            const semicolon = sourceCode.getTokenAfter(node);\n            if (semicolon && semicolon.value === ';') {\n              yield fixer.remove(semicolon);\n            }\n          },\n        });\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/test-helpers') {\n          importedSetupOnerrorName =\n            importedSetupOnerrorName ||\n            getImportIdentifier(node, '@ember/test-helpers', 'setupOnerror');\n        }\n        if (node.source.value === 'qunit') {\n          importedModuleName = importedModuleName || getImportIdentifier(node, 'qunit', 'module');\n        }\n      },\n\n      CallExpression(node) {\n        if (types.isIdentifier(node.callee) && node.callee.name === importedModuleName) {\n          isInModule = true;\n          if (node.arguments.length > 1 && node.arguments[1]) {\n            const moduleCallback = node.arguments[1];\n            hooksName =\n              moduleCallback.params &&\n              moduleCallback.params.length > 0 &&\n              types.isIdentifier(moduleCallback.params[0]) &&\n              moduleCallback.params[0].name;\n          }\n        }\n        if (types.isIdentifier(node.callee) && node.callee.name === importedSetupOnerrorName) {\n          reportErrorForNodeIfInBefore(node);\n        }\n      },\n\n      'CallExpression:exit'(node) {\n        if (types.isIdentifier(node.callee) && node.callee.name === importedModuleName) {\n          isInModule = false;\n          hooksName = undefined;\n        }\n      },\n\n      // potentially entering a `beforeEach` hook\n      'CallExpression[callee.property.name=\"beforeEach\"]'(node) {\n        if (\n          isInModule &&\n          hooksName &&\n          types.isIdentifier(node.callee.object) &&\n          node.callee.object.name === hooksName\n        ) {\n          isInBeforeEachHook = true;\n        }\n      },\n\n      // potentially exiting a `beforeEach` hook\n      'CallExpression[callee.property.name=\"beforeEach\"]:exit'() {\n        if (isInBeforeEachHook) {\n          isInBeforeEachHook = false;\n          hooksName = undefined;\n        }\n      },\n\n      // potentially entering a `before` hook\n      'CallExpression[callee.property.name=\"before\"]'(node) {\n        if (\n          isInModule &&\n          hooksName &&\n          types.isIdentifier(node.callee.object) &&\n          node.callee.object.name === hooksName\n        ) {\n          isInBeforeHook = true;\n        }\n      },\n\n      // potentially exiting a `before` hook\n      'CallExpression[callee.property.name=\"before\"]:exit'() {\n        if (isInBeforeHook) {\n          isInBeforeHook = false;\n          hooksName = undefined;\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-observers.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// General rule - Don't use observers\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = \"Don't use observers\";\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of observers',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-observers.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    let importedEmberName = undefined;\n    let importedObserverName = undefined;\n    let importedObservesName = undefined;\n    let importedAddObserverName = undefined;\n\n    return {\n      CallExpression(node) {\n        const { callee } = node;\n        if (\n          // observer();\n          types.isIdentifier(callee) &&\n          callee.name === importedObserverName\n        ) {\n          report(node);\n        } else if (\n          // Ember.observer();\n          types.isMemberExpression(callee) &&\n          types.isIdentifier(callee.object) &&\n          callee.object.name === importedEmberName &&\n          types.isIdentifier(callee.property) &&\n          callee.property.name === 'observer'\n        ) {\n          report(node);\n        } else if (\n          // this.addObserver();\n          // Ember.addObserver();\n          // foo.addObserver();\n          types.isMemberExpression(callee) &&\n          types.isIdentifier(callee.property) &&\n          callee.property.name === 'addObserver'\n        ) {\n          report(node);\n        } else if (\n          // addObserver();\n          types.isIdentifier(callee) &&\n          callee.name === importedAddObserverName\n        ) {\n          report(node);\n        }\n      },\n\n      Decorator(node) {\n        if (ember.isObserverDecorator(node, importedObservesName)) {\n          report(node);\n        }\n      },\n\n      ImportDeclaration(node) {\n        switch (node.source.value) {\n          case 'ember': {\n            importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n\n            break;\n          }\n          case '@ember/object': {\n            importedObserverName =\n              importedObserverName || getImportIdentifier(node, '@ember/object', 'observer');\n\n            break;\n          }\n          case '@ember-decorators/object': {\n            importedObservesName =\n              importedObservesName ||\n              getImportIdentifier(node, '@ember-decorators/object', 'observes');\n\n            break;\n          }\n          case '@ember/object/observers': {\n            importedAddObserverName =\n              importedAddObserverName ||\n              getImportIdentifier(node, '@ember/object/observers', 'addObserver');\n\n            break;\n          }\n          // No default\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-old-shims.js",
    "content": "'use strict';\n\n// inlined from\n// https://github.com/ember-cli/ember-rfc176-data/blob/v0.2.7/old-shims.json\n// the newer format of ember-rfc176-data no longer supports the distinction\n// between \"old shim\" and \"generally deprecated\"\nconst oldShimsData = {\n  'ember-application': {\n    default: ['@ember/application'],\n  },\n  'ember-array': {\n    default: ['@ember/array'],\n  },\n  'ember-array/mutable': {\n    default: ['@ember/array/mutable'],\n  },\n  'ember-array/utils': {\n    A: ['@ember/array', 'A'],\n    isEmberArray: ['@ember/array', 'isArray'],\n    wrap: ['@ember/array', 'makeArray'],\n  },\n  'ember-component': {\n    default: ['@ember/component'],\n  },\n  'ember-components/checkbox': {\n    default: ['@ember/component/checkbox'],\n  },\n  'ember-components/text-area': {\n    default: ['@ember/component/text-area'],\n  },\n  'ember-components/text-field': {\n    default: ['@ember/component/text-field'],\n  },\n  'ember-controller': {\n    default: ['@ember/controller'],\n  },\n  'ember-controller/inject': {\n    default: ['@ember/controller', 'inject'],\n  },\n  'ember-controller/proxy': {\n    default: ['@ember/array/proxy'],\n  },\n  'ember-controllers/sortable': {\n    default: null,\n  },\n  'ember-debug': {\n    log: ['@ember/debug', 'debug'],\n    inspect: ['@ember/debug', 'inspect'],\n    run: ['@ember/debug', 'runInDebug'],\n    warn: ['@ember/debug', 'warn'],\n  },\n  'ember-debug/container-debug-adapter': {\n    default: ['@ember/debug/container-debug-adapter'],\n  },\n  'ember-debug/data-adapter': {\n    default: ['@ember/debug/data-adapter'],\n  },\n  'ember-deprecations': {\n    deprecate: ['@ember/application/deprecations', 'deprecate'],\n    deprecateFunc: ['@ember/application/deprecations', 'deprecateFunc'],\n  },\n  'ember-enumerable': {\n    default: ['@ember/enumerable'],\n  },\n  'ember-evented': {\n    default: ['@ember/object/evented'],\n  },\n  'ember-evented/on': {\n    default: ['@ember/object/evented', 'on'],\n  },\n  'ember-globals-resolver': {\n    default: ['@ember/application/globals-resolver', null, 'GlobalsResolver'],\n  },\n  'ember-helper': {\n    default: ['@ember/component/helper'],\n    helper: ['@ember/component/helper', 'helper'],\n  },\n  'ember-instrumentation': {\n    instrument: ['@ember/instrumentation', 'instrument'],\n    reset: ['@ember/instrumentation', 'reset'],\n    subscribe: ['@ember/instrumentation', 'subscribe'],\n    unsubscribe: ['@ember/instrumentation', 'unsubscribe'],\n  },\n  'ember-locations/hash': {\n    default: ['@ember/routing/hash-location'],\n  },\n  'ember-locations/history': {\n    default: ['@ember/routing/history-location'],\n  },\n  'ember-locations/none': {\n    default: ['@ember/routing/none-location'],\n  },\n  'ember-map': {\n    default: ['@ember/map'],\n    withDefault: ['@ember/map/with-default'],\n  },\n  'ember-metal/destroy': {\n    default: null,\n  },\n  'ember-metal/events': {\n    addListener: ['@ember/object/events', 'addListener'],\n    removeListener: ['@ember/object/events', 'removeListener'],\n    send: ['@ember/object/events', 'sendEvent'],\n  },\n  'ember-metal/get': {\n    default: ['@ember/object', 'get'],\n    getProperties: ['@ember/object', 'getProperties'],\n  },\n  'ember-metal/mixin': {\n    default: ['@ember/object/mixin'],\n  },\n  'ember-metal/observer': {\n    default: ['@ember/object', 'observer'],\n    addObserver: ['@ember/object/observers', 'addObserver'],\n    removeObserver: ['@ember/object/observers', 'removeObserver'],\n  },\n  'ember-metal/on-load': {\n    default: ['@ember/application', 'onLoad'],\n    run: ['@ember/application', 'runLoadHooks'],\n  },\n  'ember-metal/set': {\n    default: ['@ember/object', 'set'],\n    setProperties: ['@ember/object', 'setProperties'],\n    trySet: ['@ember/object', 'trySet'],\n  },\n  'ember-metal/utils': {\n    aliasMethod: ['@ember/object', 'aliasMethod'],\n    assert: ['@ember/debug', 'assert'],\n    cacheFor: ['@ember/object/internals', 'cacheFor'],\n    copy: ['@ember/object/internals', 'copy'],\n    guidFor: ['@ember/object/internals', 'guidFor'],\n  },\n  'ember-object': {\n    default: ['@ember/object'],\n  },\n  'ember-owner/get': {\n    default: ['@ember/application', 'getOwner'],\n  },\n  'ember-owner/set': {\n    default: ['@ember/application', 'setOwner'],\n  },\n  'ember-platform': {\n    assign: ['@ember/polyfills', 'assign'],\n    create: ['@ember/polyfills', 'create'],\n    hasAccessors: ['@ember/polyfills', 'hasPropertyAccessors'],\n    keys: ['@ember/polyfills', 'keys'],\n  },\n  'ember-route': {\n    default: ['@ember/routing/route'],\n  },\n  'ember-router': {\n    default: ['@ember/routing/router'],\n  },\n  'ember-runloop': {\n    default: ['@ember/runloop', 'run'],\n    begin: ['@ember/runloop', 'begin'],\n    bind: ['@ember/runloop', 'bind'],\n    cancel: ['@ember/runloop', 'cancel'],\n    debounce: ['@ember/runloop', 'debounce'],\n    end: ['@ember/runloop', 'end'],\n    join: ['@ember/runloop', 'join'],\n    later: ['@ember/runloop', 'later'],\n    next: ['@ember/runloop', 'next'],\n    once: ['@ember/runloop', 'once'],\n    schedule: ['@ember/runloop', 'schedule'],\n    scheduleOnce: ['@ember/runloop', 'scheduleOnce'],\n    throttle: ['@ember/runloop', 'throttle'],\n  },\n  'ember-service': {\n    default: ['@ember/service'],\n  },\n  'ember-service/inject': {\n    default: ['@ember/service', 'inject'],\n  },\n  'ember-set/ordered': {\n    default: null,\n  },\n  'ember-string': {\n    camelize: ['@ember/string', 'camelize'],\n    capitalize: ['@ember/string', 'capitalize'],\n    classify: ['@ember/string', 'classify'],\n    dasherize: ['@ember/string', 'dasherize'],\n    decamelize: ['@ember/string', 'decamelize'],\n    fmt: ['@ember/string', 'fmt'],\n    htmlSafe: ['@ember/string', 'htmlSafe'],\n    loc: ['@ember/string', 'loc'],\n    underscore: ['@ember/string', 'underscore'],\n    w: ['@ember/string', 'w'],\n  },\n  'ember-utils': {\n    isBlank: ['@ember/utils', 'isBlank'],\n    isEmpty: ['@ember/utils', 'isEmpty'],\n    isNone: ['@ember/utils', 'isNone'],\n    isPresent: ['@ember/utils', 'isPresent'],\n    tryInvoke: ['@ember/utils', 'tryInvoke'],\n    typeOf: ['@ember/utils', 'typeOf'],\n  },\n  'ember-computed': {\n    default: ['@ember/object', 'computed'],\n    empty: ['@ember/object/computed', 'empty'],\n    notEmpty: ['@ember/object/computed', 'notEmpty'],\n    none: ['@ember/object/computed', 'none'],\n    not: ['@ember/object/computed', 'not'],\n    bool: ['@ember/object/computed', 'bool'],\n    match: ['@ember/object/computed', 'match'],\n    equal: ['@ember/object/computed', 'equal'],\n    gt: ['@ember/object/computed', 'gt'],\n    gte: ['@ember/object/computed', 'gte'],\n    lt: ['@ember/object/computed', 'lt'],\n    lte: ['@ember/object/computed', 'lte'],\n    alias: ['@ember/object/computed', 'alias'],\n    oneWay: ['@ember/object/computed', 'oneWay'],\n    reads: ['@ember/object/computed', 'reads'],\n    readOnly: ['@ember/object/computed', 'readOnly'],\n    deprecatingAlias: ['@ember/object/computed', 'deprecatingAlias'],\n    and: ['@ember/object/computed', 'and'],\n    or: ['@ember/object/computed', 'or'],\n    collect: ['@ember/object/computed', 'collect'],\n    sum: ['@ember/object/computed', 'sum'],\n    min: ['@ember/object/computed', 'min'],\n    max: ['@ember/object/computed', 'max'],\n    map: ['@ember/object/computed', 'map'],\n    sort: ['@ember/object/computed', 'sort'],\n    setDiff: ['@ember/object/computed', 'setDiff'],\n    mapBy: ['@ember/object/computed', 'mapBy'],\n    mapProperty: ['@ember/object/computed', 'mapProperty'],\n    filter: ['@ember/object/computed', 'filter'],\n    filterBy: ['@ember/object/computed', 'filterBy'],\n    filterProperty: ['@ember/object/computed', 'filterProperty'],\n    uniq: ['@ember/object/computed', 'uniq'],\n    union: ['@ember/object/computed', 'union'],\n    intersect: ['@ember/object/computed', 'intersect'],\n  },\n  'ember-test': {\n    default: null,\n  },\n  'ember-test/adapter': {\n    default: ['@ember/test/adapter'],\n  },\n  'ember-test/qunit-adapter': {\n    default: null,\n  },\n};\n\n//------------------------------------------------------------------------------\n// General rule - Don't use import paths from ember-cli-shims\n//------------------------------------------------------------------------------\n\nconst { buildFix } = require('../utils/new-module');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of old shims for modules',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-old-shims.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  create(context) {\n    const message = \"Don't use import paths from ember-cli-shims\";\n\n    return {\n      ImportDeclaration(node) {\n        if (!(node.source.value in oldShimsData)) {\n          return;\n        }\n\n        const fix = buildFix(node, oldShimsData);\n        context.report({ node, message, fix });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-on-calls-in-components.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst utils = require('../utils/utils');\nconst types = require('../utils/types');\n\n//----------------------------------------------\n// Don’t use .on() for component lifecycle events.\n//----------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of `on` to call lifecycle hooks in components',\n      category: 'Components',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-on-calls-in-components.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    const message = \"Don't use .on() for component lifecycle events.\";\n    const lifecycleHooks = new Set([\n      'init',\n      'didUpdateAttrs',\n      'didReceiveAttrs',\n      'willUpdate',\n      'willRender',\n      'didInsertElement',\n      'didUpdate',\n      'didRender',\n      'willDestroyElement',\n      'willClearRender',\n      'didDestroyElement',\n    ]);\n\n    const isOnCall = function (node) {\n      if (!node.value) {\n        return false;\n      }\n      const value = node.value;\n      const callee = value.callee;\n      const args = utils.parseArgs(value);\n\n      return (\n        types.isCallExpression(value) &&\n        lifecycleHooks.has(args[0]) &&\n        ((types.isIdentifier(callee) && callee.name === 'on') ||\n          (types.isMemberExpression(callee) &&\n            types.isIdentifier(callee.object) &&\n            callee.object.name === 'Ember' &&\n            types.isIdentifier(callee.property) &&\n            callee.property.name === 'on'))\n      );\n    };\n\n    const report = function (node) {\n      context.report({ node, message });\n    };\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (!ember.isEmberComponent(context, node)) {\n          return;\n        }\n\n        const propertiesWithOnCalls = ember\n          .getModuleProperties(node, scopeManager)\n          .filter(isOnCall);\n\n        if (propertiesWithOnCalls.length > 0) {\n          for (const property of propertiesWithOnCalls) {\n            report(property.value);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-pause-test.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Do not commit `pauseTest()`';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of the `pauseTest` helper in tests',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-pause-test.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    if (!emberUtils.isTestFile(context.filename)) {\n      return {};\n    }\n\n    let importedPauseTestName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/test-helpers') {\n          importedPauseTestName =\n            importedPauseTestName || getImportIdentifier(node, '@ember/test-helpers', 'pauseTest');\n        }\n      },\n\n      CallExpression(node) {\n        const { callee } = node;\n\n        if (\n          (types.isIdentifier(callee) && callee.name === importedPauseTestName) ||\n          (types.isThisExpression(callee.object) &&\n            types.isIdentifier(callee.property) &&\n            callee.property.name === 'pauseTest')\n        ) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-private-routing-service.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst decoratorUtils = require('../utils/decorators');\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE =\n  \"Don't inject the private '-routing' service. Instead use the public 'router' service.\";\n\nconst ROUTER_MICROLIB_ERROR_MESSAGE =\n  \"Don't access the `_routerMicrolib` as it is a private API. Instead use the public 'router' service.\";\n\nconst ROUTER_MAIN_ERROR_MESSAGE =\n  \"Don't access `router:main` as it is a private API. Instead use the public 'router' service.\";\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow injecting the private routing service',\n      category: 'Routes',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-private-routing-service.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          catchRouterMicrolib: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should catch usages of the private property `_routerMicrolib`.',\n          },\n          catchRouterMain: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should catch usages of the private property `router:main`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE,\n  ROUTER_MICROLIB_ERROR_MESSAGE,\n  ROUTER_MAIN_ERROR_MESSAGE,\n\n  create(context) {\n    const ROUTING_SERVICE_NAME = '-routing';\n    const ROUTER_MICROLIB_NAME = '_routerMicrolib';\n\n    const catchRouterMicrolib = context.options[0] ? context.options[0].catchRouterMicrolib : true;\n    const catchRouterMain = context.options[0] ? context.options[0].catchRouterMain : true;\n\n    let importedInjectName;\n    let importedEmberName;\n\n    // Handle either ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).\n    function visitClassPropertyOrPropertyDefinition(node, importedInjectName) {\n      if (\n        !node.decorators ||\n        !decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(node, importedInjectName)\n      ) {\n        return;\n      }\n\n      const hasRoutingServiceDecorator = node.decorators.some((decorator) => {\n        const expression = decorator.expression;\n        return (\n          expression &&\n          expression.arguments &&\n          expression.arguments.length > 0 &&\n          expression.arguments[0].value === ROUTING_SERVICE_NAME\n        );\n      });\n\n      if (hasRoutingServiceDecorator) {\n        context.report({ node, message: PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE });\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n      Property(node) {\n        if (\n          emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName) &&\n          node.value.arguments.length > 0 &&\n          node.value.arguments[0].value === ROUTING_SERVICE_NAME\n        ) {\n          context.report({ node, message: PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE });\n        }\n      },\n\n      ClassProperty(node) {\n        visitClassPropertyOrPropertyDefinition(node, importedInjectName);\n      },\n      PropertyDefinition(node) {\n        visitClassPropertyOrPropertyDefinition(node, importedInjectName);\n      },\n\n      Literal(node) {\n        if (\n          catchRouterMicrolib &&\n          typeof node.value === 'string' &&\n          node.value.includes(ROUTER_MICROLIB_NAME)\n        ) {\n          context.report({ node, message: ROUTER_MICROLIB_ERROR_MESSAGE });\n        }\n      },\n\n      Identifier(node) {\n        if (catchRouterMicrolib && node.name === ROUTER_MICROLIB_NAME) {\n          context.report({ node, message: ROUTER_MICROLIB_ERROR_MESSAGE });\n        }\n      },\n\n      CallExpression(node) {\n        checkForRouterMain(node);\n      },\n\n      OptionalCallExpression(node) {\n        checkForRouterMain(node);\n      },\n    };\n\n    function checkForRouterMain(node) {\n      // Looks for expressions like these:\n      // x.lookup('router:main')\n      // x.y.lookup('router:main')\n      // x?.lookup('router:main')\n      if (\n        catchRouterMain &&\n        (types.isMemberExpression(node.callee) || types.isOptionalMemberExpression(node.callee)) &&\n        types.isIdentifier(node.callee.property) &&\n        node.callee.property.name === 'lookup' &&\n        node.arguments.length === 1 &&\n        types.isLiteral(node.arguments[0]) &&\n        typeof node.arguments[0].value === 'string' &&\n        node.arguments[0].value === 'router:main'\n      ) {\n        context.report({ node, message: ROUTER_MAIN_ERROR_MESSAGE });\n      }\n    }\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-proxies.js",
    "content": "'use strict';\n\nconst ERROR_MESSAGE = 'Do not use array or object proxies.';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow using array or object proxies',\n      category: 'Ember Object',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-proxies.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      ImportDeclaration(node) {\n        if (\n          node.source.value === '@ember/object/proxy' ||\n          node.source.value === '@ember/array/proxy'\n        ) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-replace-test-comments.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\n\nconst ERROR_MESSAGE = \"No 'Replace this with your real tests' comments. Add a substantive test.\";\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: \"disallow 'Replace this with your real tests' comments in test files\",\n      category: 'Testing',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-replace-test-comments.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    //----------------------------------------------------------------------\n    // Helpers\n    //----------------------------------------------------------------------\n\n    const checkForRealTestsComment = (node) => {\n      const { value = '' } = node || {};\n      const regex = /replace this with your real tests/i;\n      const message = ERROR_MESSAGE;\n      if (regex.test(value)) {\n        context.report({ node, message });\n      }\n    };\n\n    //----------------------------------------------------------------------\n    // Public\n    //----------------------------------------------------------------------\n\n    if (!emberUtils.isTestFile(context.filename)) {\n      return {};\n    }\n    return {\n      Program(/* node */) {\n        const sourceCode = context.sourceCode || {};\n        const comments = sourceCode.getAllComments() || [];\n        for (const comment of comments) {\n          checkForRealTestsComment(comment);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-restricted-property-modifications.js",
    "content": "'use strict';\n\nconst { isStringLiteral } = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\nconst { isThisSet: isThisSetNative } = require('../utils/property-setter');\nconst { nodeToDependentKey } = require('../utils/property-getter');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow modifying the specified properties',\n      category: 'Miscellaneous',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-restricted-property-modifications.md',\n    },\n    fixable: 'code',\n    schema: {\n      type: 'array',\n      minItems: 1,\n      maxItems: 1,\n      items: [\n        {\n          type: 'object',\n          properties: {\n            properties: {\n              type: 'array',\n              items: {\n                type: 'string',\n              },\n              minItems: 1,\n              uniqueItems: true,\n              description:\n                'Array of names of properties that should not be modified (modifying child/nested/sub-properties of these is also not allowed).',\n            },\n          },\n          required: ['properties'],\n          additionalProperties: false,\n        },\n      ],\n    },\n    messages: {\n      doNotUseAssignment: 'Do not use assignment on properties that should not be modified.',\n      doNotUseSet: 'Do not call `set` on properties that should not be modified.',\n      useReadOnlyMacro:\n        'Use the `readOnly` computed property macro for properties that should not be modified.',\n    },\n  },\n  create(context) {\n    let importedComputedName;\n    let importedReadsName;\n    let importedAliasName;\n\n    const readOnlyProperties = context.options[0].properties;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        } else if (node.source.value === '@ember/object/computed') {\n          importedReadsName =\n            importedReadsName || getImportIdentifier(node, '@ember/object/computed', 'reads');\n          importedAliasName =\n            importedAliasName || getImportIdentifier(node, '@ember/object/computed', 'alias');\n        }\n      },\n\n      AssignmentExpression(node) {\n        if (!isThisSetNative(node)) {\n          return;\n        }\n\n        const dependentKey = nodeToDependentKey(node.left, context);\n        if (\n          readOnlyProperties.includes(dependentKey) ||\n          readOnlyProperties.some((property) => dependentKey.startsWith(`${property}.`))\n        ) {\n          context.report({\n            node,\n            messageId: 'doNotUseAssignment',\n          });\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          isReadOnlyPropertyUsingAliasOrReads(\n            node,\n            readOnlyProperties,\n            importedComputedName,\n            importedAliasName,\n            importedReadsName\n          )\n        ) {\n          context.report({\n            node,\n            messageId: 'useReadOnlyMacro',\n            fix(fixer) {\n              const argumentText0 = context.sourceCode.getText(node.arguments[0]);\n              return node.callee.type === 'MemberExpression'\n                ? fixer.replaceText(node, `${importedComputedName}.readOnly(${argumentText0})`)\n                : fixer.replaceText(node, `readOnly(${argumentText0})`);\n            },\n          });\n        } else if (isThisSetReadOnlyProperty(node, readOnlyProperties)) {\n          context.report({ node, messageId: 'doNotUseSet' });\n        }\n      },\n    };\n  },\n};\n\nfunction isReadOnlyPropertyUsingAliasOrReads(\n  node,\n  readOnlyProperties,\n  importedComputedName,\n  importedAliasName,\n  importedReadsName\n) {\n  // Looks for: reads('readOnlyProperty') or alias('readOnlyProperty')\n  return (\n    (isAliasComputedProperty(node, importedComputedName, importedAliasName) ||\n      isReadsComputedProperty(node, importedComputedName, importedReadsName)) &&\n    node.arguments.length === 1 &&\n    isStringLiteral(node.arguments[0]) &&\n    isReadOnlyProperty(node.arguments[0].value, readOnlyProperties)\n  );\n}\n\nfunction isThisSet(node) {\n  // Looks for: this.set('readOnlyProperty...', ...);\n  return (\n    node.callee.type === 'MemberExpression' &&\n    node.callee.object.type === 'ThisExpression' &&\n    node.callee.property.type === 'Identifier' &&\n    node.callee.property.name === 'set' &&\n    node.arguments.length === 2 &&\n    isStringLiteral(node.arguments[0])\n  );\n}\n\nfunction isThisSetReadOnlyProperty(node, readOnlyProperties) {\n  return isThisSet(node) && isReadOnlyProperty(node.arguments[0].value, readOnlyProperties);\n}\n\nfunction isAliasComputedProperty(node, importedComputedName, importedAliasName) {\n  return (\n    isIdentifierCall(node, importedAliasName) ||\n    isMemberExpressionCall(node, importedComputedName, 'alias')\n  );\n}\n\nfunction isReadsComputedProperty(node, importedComputedName, importedReadsName) {\n  return (\n    isIdentifierCall(node, importedReadsName) ||\n    isMemberExpressionCall(node, importedComputedName, 'reads')\n  );\n}\n\nfunction isIdentifierCall(node, name) {\n  return (\n    node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === name\n  );\n}\n\nfunction isMemberExpressionCall(node, object, name) {\n  return (\n    node.type === 'CallExpression' &&\n    node.callee.type === 'MemberExpression' &&\n    node.callee.object.type === 'Identifier' &&\n    node.callee.object.name === object &&\n    node.callee.property.type === 'Identifier' &&\n    node.callee.property.name === name\n  );\n}\n\nfunction isReadOnlyProperty(property, readOnlyProperties) {\n  return (\n    readOnlyProperties.includes(property) ||\n    readOnlyProperties.some((propertyCurrent) => property.startsWith(`${propertyCurrent}.`))\n  );\n}\n"
  },
  {
    "path": "lib/rules/no-restricted-resolver-tests.js",
    "content": "'use strict';\n\nconst utils = require('../utils/utils');\nconst types = require('../utils/types');\n\n//------------------------------------------------------------------------------------------------\n// General Rule - Don't use constructs or configuration that use the restricted resolver in tests.\n//------------------------------------------------------------------------------------------------\n\nfunction getNoUnitTrueMessage(fn) {\n  return `Do not use ${fn} with \\`unit: true\\``;\n}\n\nfunction getNoNeedsMessage(fn) {\n  return `Do not use ${fn} with \\`needs: [...]\\``;\n}\n\nfunction getSingleStringArgumentMessage(fn) {\n  return `Do not use ${fn} with a single string argument (implies unit: true)`;\n}\n\nfunction getNoPOJOWithoutIntegrationTrueMessage(fn) {\n  return `Do not use ${fn} whose last parameter is an object unless used in conjunction with \\`integration: true\\``;\n}\n\nfunction hasOnlyStringArgument(node) {\n  const ancestorMethodCall = getAncestorCallExpression(node);\n  const args = ancestorMethodCall.arguments;\n\n  return args.length === 1 && types.isLiteral(args[0]);\n}\n\nfunction hasUnitTrue(node) {\n  return node.properties.some(\n    (property) =>\n      types.isProperty(property) && property.key.name === 'unit' && property.value.value === true\n  );\n}\n\nfunction hasNeeds(node) {\n  return node.properties.some(\n    (property) =>\n      types.isProperty(property) &&\n      property.key.name === 'needs' &&\n      property.value.type === 'ArrayExpression'\n  );\n}\n\nfunction hasIntegrationTrue(node) {\n  return node.properties.some(\n    (property) =>\n      types.isProperty(property) &&\n      property.key.name === 'integration' &&\n      property.value.value === true\n  );\n}\n\nfunction getAncestorCallExpression(node) {\n  return utils.getAncestor(node, (ancestor) => ancestor.type === 'CallExpression');\n}\n\nfunction getLastArgument(node) {\n  const ancestorMethodCall = utils.getAncestor(\n    node,\n    (ancestor) => ancestor.type === 'CallExpression'\n  );\n  const args = ancestorMethodCall.arguments;\n  const lastArgument = args.at(-1);\n\n  return lastArgument;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow the use of patterns that use the restricted resolver in tests',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-restricted-resolver-tests.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGES: {\n    getNoUnitTrueMessage,\n    getNoNeedsMessage,\n    getSingleStringArgumentMessage,\n    getNoPOJOWithoutIntegrationTrueMessage,\n  },\n\n  create(context) {\n    let importedNames = [];\n\n    function addToImportedNames(node) {\n      importedNames = [\n        ...importedNames,\n        ...node.parent.specifiers.map((specifier) => specifier.imported.name),\n      ];\n    }\n\n    const visitors = {\n      'ImportDeclaration > Literal[value=\"ember-qunit\"]': addToImportedNames,\n      'ImportDeclaration > Literal[value=\"ember-mocha\"]': addToImportedNames,\n    };\n\n    for (const fn of [\n      'moduleFor',\n      'moduleForComponent',\n      'moduleForModel',\n      'setupTest',\n      'setupComponentTest',\n      'setupModelTest',\n    ]) {\n      visitors[`CallExpression > Identifier[name=\"${fn}\"]`] = function (node) {\n        if (!importedNames.includes(fn)) {\n          return;\n        }\n\n        if (hasOnlyStringArgument(node)) {\n          context.report({ node, message: getSingleStringArgumentMessage(fn) });\n          return;\n        }\n\n        const lastArgument = getLastArgument(node);\n        const lastArgumentIsObject = types.isObjectExpression(lastArgument);\n\n        if (lastArgumentIsObject && hasUnitTrue(lastArgument)) {\n          context.report({ node: lastArgument, message: getNoUnitTrueMessage(fn) });\n          return;\n        }\n\n        if (lastArgumentIsObject && hasNeeds(lastArgument)) {\n          context.report({ node: lastArgument, message: getNoNeedsMessage(fn) });\n          return;\n        }\n\n        if (lastArgumentIsObject && !hasIntegrationTrue(lastArgument)) {\n          context.report({\n            node: lastArgument,\n            message: getNoPOJOWithoutIntegrationTrueMessage(fn),\n          });\n        }\n      };\n    }\n\n    return visitors;\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-restricted-service-injections.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst emberUtils = require('../utils/ember');\nconst decoratorUtils = require('../utils/decorators');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst DEFAULT_ERROR_MESSAGE = 'Injecting this service is not allowed from this file.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow injecting certain services under certain paths',\n      category: 'Services',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-restricted-service-injections.md',\n    },\n    schema: {\n      type: 'array',\n      uniqueItems: true,\n      minItems: 1,\n      items: [\n        {\n          type: 'object',\n          required: ['services'],\n          properties: {\n            paths: {\n              type: 'array',\n              uniqueItems: true,\n              minItems: 1,\n              items: {\n                type: 'string',\n              },\n              description:\n                'Optional list of regexp file paths that injecting the specified services should be disallowed under (omit this field to match any path) (for glob patterns, use [ESLint `overrides`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns) instead).',\n            },\n            services: {\n              type: 'array',\n              uniqueItems: true,\n              minItems: 1,\n              items: {\n                type: 'string',\n              },\n              description:\n                'List of (kebab-case) service names that should be disallowed from being injected under the specified paths.',\n            },\n            message: {\n              type: 'string',\n              description: 'Optional custom error message to display for violations.',\n            },\n          },\n          additionalProperties: false,\n        },\n      ],\n    },\n  },\n\n  DEFAULT_ERROR_MESSAGE,\n\n  create(context) {\n    // Validate options.\n    for (const option of context.options) {\n      for (const service of option.services) {\n        assert(\n          service.toLowerCase() === service,\n          'Service name should be passed in kebab-case (all lower case)'\n        );\n      }\n    }\n\n    // Find matching denylist entries for this file path.\n    const denylists = context.options.filter(\n      (option) => !option.paths || option.paths.some((path) => context.filename.match(path))\n    );\n\n    if (denylists.length === 0) {\n      return {};\n    }\n\n    function checkForViolationAndReport(node, serviceName) {\n      const serviceNameKebabCase = emberUtils.convertServiceNameToKebabCase(serviceName); // splitting is used to avoid converting folder/ to folder-\n\n      for (const denylist of denylists) {\n        // Denylist services are always passed in in kebab-case, so we can do a kebab-case comparison.\n        if (denylist.services.includes(serviceNameKebabCase)) {\n          context.report({\n            node,\n            message: denylist.message || DEFAULT_ERROR_MESSAGE,\n          });\n        }\n      }\n    }\n\n    let importedInjectName;\n    let importedEmberName;\n\n    // Handle either ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).\n    // Handles:\n    // * @service myService\n    // * @service() myService\n    // * @service('myService') propertyName\n    function visitClassPropertyOrPropertyDefinition(node) {\n      if (!emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)) {\n        return;\n      }\n\n      // Find the service decorator.\n      const serviceDecorator =\n        decoratorUtils.findDecorator(node, 'service') ||\n        decoratorUtils.findDecorator(node, 'inject') ||\n        decoratorUtils.findDecorator(node, 'Ember.inject.service');\n\n      if (!serviceDecorator) {\n        // TODO: emberUtils.isInjectedServiceProp() must have detected an injection that findDecorator() didn't.\n        return;\n      }\n\n      // Get the service name either from the string argument or from the property name.\n      const serviceName =\n        serviceDecorator.expression.type === 'CallExpression' &&\n        serviceDecorator.expression.arguments &&\n        serviceDecorator.expression.arguments.length === 1 &&\n        serviceDecorator.expression.arguments[0].type === 'Literal' &&\n        typeof serviceDecorator.expression.arguments[0].value === 'string'\n          ? serviceDecorator.expression.arguments[0].value\n          : node.key.name || node.key.value;\n\n      checkForViolationAndReport(node, serviceName);\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n      // Handles:\n      // * myService: service()\n      // * propertyName: service('myService')\n      Property(node) {\n        if (!emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)) {\n          return;\n        }\n\n        const callExpression = node.value;\n\n        // Get the service name either from the string argument or from the property name.\n        if (callExpression.arguments && callExpression.arguments.length > 0) {\n          if (\n            callExpression.arguments[0].type === 'Literal' &&\n            typeof callExpression.arguments[0].value === 'string'\n          ) {\n            // The service name is the string argument.\n            checkForViolationAndReport(node, callExpression.arguments[0].value);\n          } else {\n            // Ignore this case since the argument is not a string.\n          }\n        } else {\n          // The service name is the property name.\n          checkForViolationAndReport(node, node.key.name || node.key.value);\n        }\n      },\n\n      ClassProperty: visitClassPropertyOrPropertyDefinition,\n      PropertyDefinition: visitClassPropertyOrPropertyDefinition,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-runloop.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// General rule - Don’t use runloop functions\n//------------------------------------------------------------------------------\n\n/**\n * Map of runloop functions to ember-lifeline recommended replacements\n */\nconst RUNLOOP_TO_LIFELINE_MAP = Object.freeze({\n  later: 'runTask',\n  next: 'runTask',\n  debounce: 'debounceTask',\n  schedule: 'scheduleTask',\n  throttle: 'throttleTask',\n});\n\nconst ERROR_MESSAGE =\n  \"Don't use @ember/runloop functions. Use ember-lifeline, ember-concurrency, or @ember/destroyable instead.\";\n\n// https://api.emberjs.com/ember/3.24/classes/@ember%2Frunloop\nconst EMBER_RUNLOOP_FUNCTIONS = [\n  'begin',\n  'bind',\n  'cancel',\n  'debounce',\n  'end',\n  'join',\n  'later',\n  'next',\n  'once',\n  'run',\n  'schedule',\n  'scheduleOnce',\n  'throttle',\n];\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of `@ember/runloop` functions',\n      category: 'Miscellaneous',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-runloop.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowList: {\n            type: 'array',\n            uniqueItems: true,\n            items: {\n              type: 'string',\n              enum: EMBER_RUNLOOP_FUNCTIONS,\n              minItems: 1,\n            },\n            description:\n              'If you have `@ember/runloop` functions that you wish to allow, you can configure this rule to allow specific methods. The configuration takes an object with the `allowList` property, which is an array of strings where the strings must be names of runloop functions.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      main: ERROR_MESSAGE,\n      lifelineReplacement: `${ERROR_MESSAGE} For this case, you can replace \\`{{actualMethodUsed}}\\` with \\`{{lifelineEquivalent}}\\` from ember-lifeline.`,\n    },\n  },\n\n  create(context) {\n    // List of allowed runloop functions\n    const allowList = context.options[0]?.allowList ?? [];\n    // Maps local names to imported names of imports\n    const localToImportedNameMap = new Map();\n    let namespaceImportName;\n\n    const isDisallowed = function (fn) {\n      return EMBER_RUNLOOP_FUNCTIONS.includes(fn) && !allowList.includes(fn);\n    };\n\n    /**\n     * Reports a node with usage of a disallowed runloop function\n     * @param {Node} node\n     * @param {String} [runloopFn] the name of the runloop function that is not allowed\n     * @param {String} [localName] the locally used name of the runloop function\n     */\n    const report = function (node, runloopFn, localName) {\n      if (Object.keys(RUNLOOP_TO_LIFELINE_MAP).includes(runloopFn)) {\n        // If there is a recommended lifeline replacement, include the suggestion\n        context.report({\n          node,\n          messageId: 'lifelineReplacement',\n          data: {\n            actualMethodUsed: localName,\n            lifelineEquivalent: RUNLOOP_TO_LIFELINE_MAP[runloopFn],\n          },\n        });\n      } else {\n        // Otherwise, show a generic error message\n        context.report({ node, messageId: 'main' });\n      }\n    };\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/runloop') {\n          for (const spec of node.specifiers) {\n            if (spec.type === 'ImportSpecifier') {\n              const importedName = spec.imported.name;\n              if (EMBER_RUNLOOP_FUNCTIONS.includes(importedName)) {\n                localToImportedNameMap.set(spec.local.name, importedName);\n              }\n            } else if (spec.type === 'ImportNamespaceSpecifier') {\n              namespaceImportName = spec.local.name;\n            }\n          }\n        }\n      },\n\n      CallExpression(node) {\n        const callee = node.callee;\n\n        // Examples: run(...), later(...)\n        if (callee.type === 'Identifier') {\n          const localName = callee.name;\n          const runloopFn = localToImportedNameMap.get(localName);\n\n          if (runloopFn && isDisallowed(runloopFn)) {\n            report(node, runloopFn, localName);\n          }\n\n          return;\n        }\n\n        if (\n          callee.type === 'MemberExpression' &&\n          callee.object.type === 'Identifier' &&\n          callee.property.type === 'Identifier'\n        ) {\n          const objectName = callee.object.name;\n          const methodName = callee.property.name;\n\n          // runloop functions (aside from run itself) can chain onto `run`, so we need to check for this\n          // Examples: run.later(...), run.schedule(...)\n          if (\n            localToImportedNameMap.get(objectName) === 'run' &&\n            isDisallowed(methodName) &&\n            methodName !== 'run'\n          ) {\n            report(node, methodName, `${objectName}.${methodName}`);\n            return;\n          }\n\n          // Example: `import * as runloop from '@ember/runloop'` -> `runloop.later(...)`\n          if (objectName === namespaceImportName && isDisallowed(methodName)) {\n            report(node, methodName, `${objectName}.${methodName}`);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-settled-after-test-helper.js",
    "content": "'use strict';\n\nconst ERROR_MESSAGE =\n  'Do not call `settled()` right after a test helper that already calls it internally.';\n\nconst SETTLING_TEST_HELPERS = new Set([\n  'blur',\n  'clearRender',\n  'click',\n  'doubleClick',\n  'fillIn',\n  'focus',\n  'render',\n  'settled',\n  'tap',\n  'triggerEvent',\n  'triggerKeyEvent',\n  'typeIn',\n  'visit',\n]);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'disallow usage of `await settled()` right after test helper that calls it internally',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-settled-after-test-helper.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const settlingTestHelpers = new Set();\n\n    return {\n      ImportDeclaration(node) {\n        const { source, specifiers } = node;\n        if (source.type !== 'Literal' || source.value !== '@ember/test-helpers') {\n          return;\n        }\n\n        for (const specifier of specifiers) {\n          if (specifier.type !== 'ImportSpecifier') {\n            continue;\n          }\n\n          const { imported, local } = specifier;\n          if (imported.type !== 'Identifier' || local.type !== 'Identifier') {\n            continue;\n          }\n\n          if (SETTLING_TEST_HELPERS.has(imported.name)) {\n            settlingTestHelpers.add(local.name);\n          }\n        }\n      },\n\n      ExpressionStatement(node) {\n        if (!isAwaitSettled(node)) {\n          return;\n        }\n\n        const { parent } = node;\n        /* istanbul ignore next */\n        if (!Array.isArray(parent.body)) {\n          return;\n        }\n\n        const index = parent.body.indexOf(node);\n        if (index < 1) {\n          return;\n        }\n\n        const prevStatement = parent.body[index - 1];\n        if (!isAwaitSettlingTestHelper(prevStatement, settlingTestHelpers)) {\n          return;\n        }\n\n        context.report({\n          node,\n          message: ERROR_MESSAGE,\n          fix(fixer) {\n            return fixer.remove(node);\n          },\n        });\n      },\n    };\n  },\n};\n\nfunction isAwaitSettled(node) {\n  const { expression } = node;\n  if (expression.type !== 'AwaitExpression') {\n    return false;\n  }\n\n  const { argument } = expression;\n  if (argument.type !== 'CallExpression') {\n    return false;\n  }\n\n  const { callee } = argument;\n  return callee.type === 'Identifier' && callee.name === 'settled';\n}\n\nfunction isAwaitSettlingTestHelper(node, settlingTestHelpers) {\n  if (node.type !== 'ExpressionStatement') {\n    return false;\n  }\n\n  const { expression } = node;\n  if (expression.type !== 'AwaitExpression') {\n    return false;\n  }\n\n  const { argument } = expression;\n  if (argument.type !== 'CallExpression') {\n    return false;\n  }\n\n  const { callee } = argument;\n  return callee.type === 'Identifier' && settlingTestHelpers.has(callee.name);\n}\n"
  },
  {
    "path": "lib/rules/no-shadow-route-definition.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\n\nconst ROOT_PATH_TRIM_REGEX = /\\/{2,}/g;\nconst SOURCE_PATH_VALUE_TYPE = {\n  DYNAMIC: 'dynamic',\n  STATIC: 'static',\n};\nconst URL_CHUNK_SEPARATOR = '/';\n\nfunction buildErrorMessage(opts) {\n  const { leftRoute, rightRoute } = opts;\n  return `Route ${buildRouteErrorInfo(leftRoute)} is shadowing route ${buildRouteErrorInfo(\n    rightRoute\n  )}`;\n}\n\nfunction buildSourceLocationString(routeInfo) {\n  return `${routeInfo.source.loc.start.line}L:${routeInfo.source.loc.start.column}C`;\n}\n\nfunction buildRouteErrorInfo(routeInfo) {\n  return `\"${routeInfo.name}\" (\"${routeInfo.fullPath}\", ${buildSourceLocationString(routeInfo)})`;\n}\n\nfunction isNestedRouteWithSamePath(routeInfo) {\n  return routeInfo.parent.fullPathWithGenericParams === routeInfo.route.fullPathWithGenericParams;\n}\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'enforce no route path definition shadowing',\n      category: 'Routes',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-shadow-route-definition.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  buildErrorMessage,\n\n  create(context) {\n    if (ember.isTestFile(context.filename)) {\n      // This rule does not apply to test files.\n      return {};\n    }\n\n    const routeMap = new Map();\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (!ember.isRoute(node)) {\n          return;\n        }\n\n        const routeInfo = getRouteInfo(node, scopeManager);\n        if (!isValidRouteInfo(routeInfo)) {\n          return;\n        }\n\n        const routeLookupKey = `${routeInfo.route.blockStatementsTreePrefix}::${routeInfo.route.fullPathWithGenericParams}`;\n        if (routeMap.has(routeLookupKey) && !isNestedRouteWithSamePath(routeInfo)) {\n          const existingRouteInfo = routeMap.get(routeLookupKey);\n          context.report({\n            node,\n            message: buildErrorMessage({\n              leftRoute: {\n                name: routeInfo.route.name,\n                fullPath: routeInfo.route.fullPath,\n                source: routeInfo.route.source,\n              },\n              rightRoute: {\n                name: existingRouteInfo.route.name,\n                fullPath: existingRouteInfo.route.fullPath,\n                source: existingRouteInfo.route.source,\n              },\n            }),\n          });\n          return;\n        }\n        if (!routeMap.has(routeLookupKey)) {\n          routeMap.set(routeLookupKey, routeInfo);\n        }\n      },\n    };\n  },\n};\n\nfunction getRouteInfo(node, scopeManager) {\n  const basePath = getRouteBasePath(node, scopeManager);\n  if (basePath.normalizedPath === null) {\n    return null;\n  }\n\n  const parentRoutes = getParentRoutesPaths(node, scopeManager);\n  const notSupportedParentRoutePathArguments = parentRoutes.find((routePathInfo) => {\n    return routePathInfo.normalizedPath === null;\n  });\n\n  if (notSupportedParentRoutePathArguments) {\n    return null;\n  }\n\n  const routeName = getRouteName(node).stringValue;\n\n  // We gather block statement ranges as prefix for route lookup paths\n  // Example: \"121-150-130-135\"\n  const blockStatementsTreePrefix = lookupIfElseBlockStatementsTreePrefix(node).join('-');\n\n  // We replace \"////post/something\" -> \"/post/something\".\n  // As that what nesting of / configured routes means for Ember router.\n  return {\n    route: {\n      name: routeName,\n      basePath,\n      fullPath: trimRootLevelNestedRoutes(`${convertPathForDisplay([...parentRoutes, basePath])}`),\n      fullPathWithGenericParams: trimRootLevelNestedRoutes(\n        convertPathToGenericForMatching([...parentRoutes, basePath])\n      ),\n      blockStatementsTreePrefix,\n      source: {\n        loc: node.loc,\n      },\n    },\n    parent: {\n      fullPath: trimRootLevelNestedRoutes(convertPathForDisplay(parentRoutes)),\n      fullPathWithGenericParams: trimRootLevelNestedRoutes(\n        convertPathToGenericForMatching(parentRoutes)\n      ),\n    },\n  };\n}\n\nfunction getRouteBasePath(node, scopeManager) {\n  let routePathInfo = getRouteName(node);\n  const optionsNode =\n    node.arguments.length >= 2 && getNodeOrNodeFromVariable(node.arguments[1], scopeManager);\n  const pathOptionNode =\n    optionsNode &&\n    optionsNode.type === 'ObjectExpression' &&\n    getPropertyByKeyName(optionsNode, 'path');\n  if (pathOptionNode) {\n    routePathInfo = getNodeValue(pathOptionNode.value);\n  }\n\n  let path = routePathInfo.stringValue;\n  if (isString(path) && !path.startsWith(URL_CHUNK_SEPARATOR)) {\n    path = `/${routePathInfo.stringValue.trim()}`;\n  }\n  return {\n    type: routePathInfo.type,\n    normalizedPath: path,\n    rawPath: routePathInfo.stringValue,\n  };\n}\n\nfunction getParentRoutesPaths(node, scopeManager) {\n  const parentNode = node.parent;\n  let stack = [];\n  if (parentNode) {\n    if (ember.isRoute(parentNode)) {\n      stack.push(getRouteBasePath(parentNode, scopeManager));\n    }\n    stack = [...stack, ...getParentRoutesPaths(parentNode)];\n  }\n  return stack;\n}\n\nfunction getPropertyByKeyName(objectExpression, keyName) {\n  return objectExpression.properties.find(\n    (property) =>\n      types.isProperty(property) &&\n      types.isIdentifier(property.key) &&\n      property.key.name === keyName\n  );\n}\n\nfunction trimRootLevelNestedRoutes(routesPath) {\n  return routesPath.replaceAll(ROOT_PATH_TRIM_REGEX, URL_CHUNK_SEPARATOR);\n}\n\nfunction convertPathToGenericForMatching(routePathInfos) {\n  return routePathInfos\n    .map((routePathInfo) => {\n      const convertedPathForMatchingChunks = routePathInfo.normalizedPath\n        .split(URL_CHUNK_SEPARATOR)\n        .map((pathChunk) => {\n          if (routePathInfo.type === SOURCE_PATH_VALUE_TYPE.STATIC) {\n            if (pathChunk.startsWith(':')) {\n              return ':generic-param-for-matching';\n            }\n            if (pathChunk.startsWith('*')) {\n              return '*generic-wildcard-for-matching';\n            }\n          }\n          if (routePathInfo.type === SOURCE_PATH_VALUE_TYPE.DYNAMIC) {\n            return `variable:${pathChunk}`;\n          }\n          return pathChunk;\n        });\n      return convertedPathForMatchingChunks.join(URL_CHUNK_SEPARATOR);\n    })\n    .join('');\n}\n\nfunction convertPathForDisplay(routePathInfos) {\n  return routePathInfos\n    .map((routePathInfo) => {\n      return routePathInfo.rawPath;\n    })\n    .join('');\n}\n\nfunction getRouteName(node) {\n  const [firstArgument] = node.arguments;\n  return getNodeValue(firstArgument);\n}\n\nfunction getNodeValue(node) {\n  if (types.isIdentifier(node)) {\n    return {\n      type: SOURCE_PATH_VALUE_TYPE.DYNAMIC,\n      stringValue: node.name,\n    };\n  }\n  if (types.isLiteral(node)) {\n    return {\n      type: SOURCE_PATH_VALUE_TYPE.STATIC,\n      stringValue: String(node.value),\n    };\n  }\n  return {\n    type: null,\n    stringValue: null,\n  };\n}\n\nfunction isString(value) {\n  return typeof value === 'string';\n}\n\nfunction isValidRouteInfo(routeInfo) {\n  return routeInfo !== null;\n}\n\nfunction lookupIfElseBlockStatementsTreePrefix(node) {\n  const inspectedNode = node.parent;\n  let stack = [];\n  if (inspectedNode) {\n    if (inspectedNode.type === 'BlockStatement') {\n      if (inspectedNode.parent.type === 'IfStatement') {\n        if (\n          inspectedNode.parent.consequent === inspectedNode ||\n          inspectedNode.parent.alternate === inspectedNode\n        ) {\n          stack.push(inspectedNode.range.join('-'));\n        }\n      }\n    }\n    stack = [...stack, ...lookupIfElseBlockStatementsTreePrefix(inspectedNode)];\n  }\n  return stack;\n}\n"
  },
  {
    "path": "lib/rules/no-side-effects.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst computedPropertyUtils = require('../utils/computed-properties');\nconst propertySetterUtils = require('../utils/property-setter');\nconst emberUtils = require('../utils/ember');\nconst estraverse = require('estraverse');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// General rule - Don't introduce side-effects in computed properties\n//------------------------------------------------------------------------------\n\n// Ember.set(this, 'foo', 123)\nfunction isEmberSetThis(node, importedEmberName) {\n  return (\n    types.isCallExpression(node) &&\n    types.isMemberExpression(node.callee) &&\n    types.isIdentifier(node.callee.object) &&\n    node.callee.object.name === importedEmberName &&\n    types.isIdentifier(node.callee.property) &&\n    ['set', 'setProperties'].includes(node.callee.property.name) &&\n    node.arguments.length > 0 &&\n    memberExpressionBeginsWithThis(node.arguments[0])\n  );\n}\n\n// set(this, 'foo', 123)\nfunction isImportedSetThis(node, importedSetName, importedSetPropertiesName) {\n  return (\n    types.isCallExpression(node) &&\n    types.isIdentifier(node.callee) &&\n    [importedSetName, importedSetPropertiesName].includes(node.callee.name) &&\n    node.arguments.length > 0 &&\n    memberExpressionBeginsWithThis(node.arguments[0])\n  );\n}\n\n// this.set('foo', 123)\n// this.prop.set('foo', 123)\nfunction isThisSet(node) {\n  return (\n    types.isCallExpression(node) &&\n    types.isMemberExpression(node.callee) &&\n    types.isIdentifier(node.callee.property) &&\n    ['set', 'setProperties'].includes(node.callee.property.name) &&\n    memberExpressionBeginsWithThis(node.callee.object)\n  );\n}\n\n// import { sendEvent } from \"@ember/object/events\"\n// Ember.sendEvent\n\n// Looks for variations like:\n// - this.send(...)\n// - Ember.send(...)\nconst DISALLOWED_FUNCTION_CALLS = new Set(['send', 'sendAction', 'sendEvent', 'trigger']);\nfunction isDisallowedFunctionCall(node, importedEmberName) {\n  return (\n    types.isCallExpression(node) &&\n    types.isMemberExpression(node.callee) &&\n    (types.isThisExpression(node.callee.object) ||\n      (types.isIdentifier(node.callee.object) && node.callee.object.name === importedEmberName)) &&\n    types.isIdentifier(node.callee.property) &&\n    DISALLOWED_FUNCTION_CALLS.has(node.callee.property.name)\n  );\n}\n\n// sendEvent(...)\nfunction isImportedSendEventCall(node, importedSendEventName) {\n  return (\n    types.isCallExpression(node) &&\n    types.isIdentifier(node.callee) &&\n    node.callee.name === importedSendEventName\n  );\n}\n\n/**\n * Finds:\n * - this\n * - this.foo\n * - this.foo.bar\n * - this?.foo?.bar\n * @param {node} node\n */\nfunction memberExpressionBeginsWithThis(node) {\n  if (types.isThisExpression(node)) {\n    return true;\n  } else if (types.isMemberExpression(node) || types.isOptionalMemberExpression(node)) {\n    return memberExpressionBeginsWithThis(node.object);\n  } else if (node.type === 'ChainExpression') {\n    return memberExpressionBeginsWithThis(node.expression);\n  }\n  return false;\n}\n\n/**\n * Recursively finds calls that could be side effects in a computed property function body.\n *\n * @param {ASTNode} computedPropertyBody body of computed property to search\n * @param {boolean} catchEvents\n * @param {string} importedEmberName\n * @param {string} importedSetName\n * @param {string} importedSetPropertiesName\n * @param {string} importedSendEventName\n * @returns {Array<ASTNode>}\n */\nfunction findSideEffects(\n  computedPropertyBody,\n  catchEvents,\n  importedEmberName,\n  importedSetName,\n  importedSetPropertiesName,\n  importedSendEventName\n) {\n  const results = [];\n\n  estraverse.traverse(computedPropertyBody, {\n    enter(child) {\n      if (\n        isEmberSetThis(child, importedEmberName) || // Ember.set(this, 'foo', 123)\n        isImportedSetThis(child, importedSetName, importedSetPropertiesName) || // set(this, 'foo', 123)\n        isThisSet(child) || // this.set('foo', 123)\n        propertySetterUtils.isThisSet(child) || // this.foo = 123;\n        (catchEvents && isDisallowedFunctionCall(child, importedEmberName)) || // this.send('done')\n        (catchEvents && isImportedSendEventCall(child, importedSendEventName)) // sendEvent(...)\n      ) {\n        results.push(child);\n      }\n    },\n    fallback: 'iteration',\n  });\n\n  return results;\n}\n\nconst ERROR_MESSAGE = \"Don't introduce side-effects in computed properties\";\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow unexpected side effects in computed properties',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-side-effects.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          catchEvents: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should catch function calls that send actions or events.',\n          },\n          checkPlainGetters: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should check plain (non-computed) getters in native classes for side effects.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    // Options:\n    const catchEvents = context.options[0] ? context.options[0].catchEvents : true;\n    const checkPlainGetters = !context.options[0] || context.options[0].checkPlainGetters;\n\n    let importedEmberName;\n    let importedComputedName;\n    let importedSetName;\n    let importedSetPropertiesName;\n    let importedSendEventName;\n\n    let currentEmberClass;\n\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n          importedSetName = importedSetName || getImportIdentifier(node, '@ember/object', 'set');\n          importedSetPropertiesName =\n            importedSetPropertiesName ||\n            getImportIdentifier(node, '@ember/object', 'setProperties');\n        }\n        if (node.source.value === '@ember/object/events') {\n          importedSendEventName =\n            importedSendEventName || getImportIdentifier(node, '@ember/object/events', 'sendEvent');\n        }\n      },\n\n      MethodDefinition(node) {\n        if (\n          !checkPlainGetters ||\n          !currentEmberClass ||\n          node.kind !== 'get' ||\n          !types.isFunctionExpression(node.value)\n        ) {\n          return;\n        }\n\n        for (const sideEffect of findSideEffects(\n          node.value,\n          catchEvents,\n          importedEmberName,\n          importedSetName,\n          importedSetPropertiesName,\n          importedSendEventName\n        )) {\n          report(sideEffect);\n        }\n      },\n\n      ClassDeclaration(node) {\n        if (emberUtils.isAnyEmberCoreModule(context, node)) {\n          currentEmberClass = node;\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        if (currentEmberClass === node) {\n          currentEmberClass = null;\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          (checkPlainGetters && currentEmberClass) ||\n          !emberUtils.isComputedProp(node, importedEmberName, importedComputedName)\n        ) {\n          return;\n        }\n\n        const computedPropertyBody = computedPropertyUtils.getComputedPropertyFunctionBody(node);\n\n        for (const sideEffect of findSideEffects(\n          computedPropertyBody,\n          catchEvents,\n          importedEmberName,\n          importedSetName,\n          importedSetPropertiesName,\n          importedSendEventName\n        )) {\n          report(sideEffect);\n        }\n      },\n\n      Identifier(node) {\n        if (\n          (checkPlainGetters && currentEmberClass) ||\n          !emberUtils.isComputedProp(node, importedEmberName, importedComputedName)\n        ) {\n          return;\n        }\n\n        const computedPropertyBody = computedPropertyUtils.getComputedPropertyFunctionBody(node);\n\n        for (const sideEffect of findSideEffects(\n          computedPropertyBody,\n          catchEvents,\n          importedEmberName,\n          importedSetName,\n          importedSetPropertiesName,\n          importedSendEventName\n        )) {\n          report(sideEffect);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-string-prototype-extensions.js",
    "content": "'use strict';\n\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Do not using `String` prototype extension methods.';\n\nconst EXTENSION_METHODS = new Set([\n  'camelize',\n  'capitalize',\n  'classify',\n  'dasherize',\n  'decamelize',\n  'htmlSafe',\n  'loc',\n  'underscore',\n  'w',\n]);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of `String` prototype extensions',\n      category: 'Deprecations',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-string-prototype-extensions.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedEmberName;\n\n    return {\n      CallExpression(node) {\n        const { callee } = node;\n        if (callee.type !== 'MemberExpression') {\n          return;\n        }\n\n        const { object, property } = callee;\n        if (object.type === 'ThisExpression') {\n          return;\n        }\n        if (property.type !== 'Identifier') {\n          return;\n        }\n\n        if (\n          object.type === 'MemberExpression' &&\n          object.object.type === 'Identifier' &&\n          object.object.name === importedEmberName &&\n          object.property.type === 'Identifier' &&\n          object.property.name === 'String'\n        ) {\n          // Using functions directly from \"Ember.String...\" is allowed.\n          return;\n        }\n\n        if (EXTENSION_METHODS.has(property.name)) {\n          context.report({ node: property, message: ERROR_MESSAGE });\n        }\n      },\n\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-test-and-then.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\n\nconst ERROR_MESSAGE = 'Use `await` instead of `andThen` test wait helper.';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of the `andThen` test wait helper',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-and-then.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    if (!emberUtils.isTestFile(context.filename)) {\n      return {};\n    }\n\n    return {\n      CallExpression(node) {\n        const callee = node.callee;\n        if (types.isIdentifier(callee) && callee.name === 'andThen') {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-test-import-export.js",
    "content": "/**\n * @fileOverview no importing of test files and no exporting in test files\n */\n\n'use strict';\n\nconst path = require('path');\nconst emberUtils = require('../utils/ember');\n\nconst NO_IMPORT_MESSAGE =\n  'Do not import from a test file (a file ending in \"-test.js\") in another test file. Doing so will cause the module and tests from the imported file to be executed again.';\n\nconst NO_EXPORT_MESSAGE = 'No exports from test file. Any exports should be done in a test helper.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow importing of \"-test.js\" in a test file and exporting from a test file',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-import-export.md',\n    },\n    schema: [],\n    importMessage: NO_IMPORT_MESSAGE,\n    exportMessage: NO_EXPORT_MESSAGE,\n  },\n\n  create: function create(context) {\n    const noExports = function (node) {\n      if (\n        !emberUtils.isTestFile(context.filename) ||\n        !isInTestDirectory(context.filename) ||\n        isTestHelperFilename(context.filename)\n      ) {\n        return;\n      }\n\n      context.report({\n        message: NO_EXPORT_MESSAGE,\n        node,\n      });\n    };\n\n    return {\n      ImportDeclaration(node) {\n        const importSource = node.source.value;\n\n        if (\n          importSource.startsWith('.') &&\n          importSource.endsWith('-test') &&\n          !isTestHelperFilename(path.resolve(path.dirname(context.filename), importSource))\n        ) {\n          context.report({\n            message: NO_IMPORT_MESSAGE,\n            node,\n          });\n        }\n      },\n      ExportNamedDeclaration(node) {\n        noExports(node);\n      },\n      ExportDefaultDeclaration(node) {\n        noExports(node);\n      },\n    };\n  },\n};\n\nfunction isInTestDirectory(filename) {\n  const filenameParts = path.normalize(filename).split(path.sep);\n  const testDirIndex = filenameParts.findIndex((part) => part === 'tests' || part === 'test');\n  // tests/ or test/ must be a top-level directory (index 0) or directly under\n  // a project name (index 1, e.g. my-app/tests/). Deeper nesting like\n  // app/components/tests/ is not a real test directory.\n  return testDirIndex >= 0 && testDirIndex <= 1;\n}\n\nfunction isTestHelperFilename(filename) {\n  const filenameParts = path.normalize(filename).split(path.sep);\n  for (let i = 0; i < filenameParts.length - 1; ++i) {\n    if (filenameParts[i] === 'tests' && filenameParts[i + 1] === 'helpers') {\n      return true;\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "lib/rules/no-test-module-for.js",
    "content": "'use strict';\n\nconst assert = require('assert');\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\n\nconst ERROR_MESSAGE = 'moduleFor* apis are are not allowed. Use `module` instead of `moduleFor`';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of `moduleFor`, `moduleForComponent`, etc',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-module-for.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const filename = context.filename;\n    const isTestFile =\n      emberUtils.isTestFile(filename) ||\n      (filename.includes('tests') && filename.includes('helpers'));\n\n    if (!isTestFile) {\n      return {};\n    }\n\n    return {\n      FunctionDeclaration(node) {\n        if (isModuleFor(context, node)) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n      CallExpression(node) {\n        if (isModuleFor(context, node)) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n\nfunction isModuleFor(_context, node) {\n  assert(types.isCallExpression(node) || types.isFunctionDeclaration(node));\n\n  const { callee, id } = node;\n  const identifier = callee || id;\n\n  if (!identifier || !types.isIdentifier(identifier)) {\n    return false;\n  }\n\n  // catches everything from legacy ember-qunit\n  //  - moduleForComponent\n  //  - moduleForModel\n  //  - moduleFor\n  //\n  // this lint will error on the definition of moduleForAcceptance, likely in\n  // tests/helpers/start-app.js\n  // moduleForAcceptance was the default name from the ember blueprint\n  //\n  // in case someone renames a moduleFor to something else like,\n  // - moduleForUtil\n  // - moduleForRoute\n  // the entire pattern is not allowed.\n  const isBespokeInvocation = identifier.name.startsWith('moduleFor');\n\n  return isBespokeInvocation;\n}\n"
  },
  {
    "path": "lib/rules/no-test-support-import.js",
    "content": "/**\n * @fileOverview no importing of test files and no exporting in test files\n */\n\n'use strict';\n\nconst path = require('path');\n\nconst ERROR_MESSAGE_NO_IMPORT =\n  'Do not import a file from test-support into production code, only into test files.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE_NO_IMPORT,\n\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow importing of \"test-support\" files in production code.',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-support-import.md',\n    },\n    schema: [],\n  },\n\n  create: function create(context) {\n    const fileName = context.filename;\n    const fileNameParts = path.normalize(fileName).split(path.sep);\n\n    return {\n      ImportDeclaration(node) {\n        const importSource = node.source.value;\n        const importSourceParts = path.normalize(importSource).split(path.sep);\n\n        if (\n          importSourceParts.includes('test-support') &&\n          !(\n            fileNameParts.includes('tests') ||\n            fileNameParts.includes('addon-test-support') ||\n            fileNameParts.includes('test-support')\n          )\n        ) {\n          context.report({\n            message: ERROR_MESSAGE_NO_IMPORT,\n            node,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-test-this-render.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst types = require('../utils/types');\n\nfunction makeErrorMessage(functionName) {\n  return `Do not use \\`this.${functionName}()\\`. Prefer \\`${functionName}\\` from \\`@ember/test-helpers\\` instead.`;\n}\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        \"disallow usage of the `this.render` in tests, recommending to use @ember/test-helpers' `render` instead.\",\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-this-render.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  makeErrorMessage,\n\n  create(context) {\n    if (!emberUtils.isTestFile(context.filename)) {\n      return {};\n    }\n\n    return {\n      CallExpression(node) {\n        const { callee } = node;\n\n        if (\n          types.isThisExpression(callee.object) &&\n          types.isIdentifier(callee.property) &&\n          (callee.property.name === 'render' || callee.property.name === 'clearRender')\n        ) {\n          context.report({\n            node,\n            message: makeErrorMessage(callee.property.name),\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-tracked-built-ins.js",
    "content": "'use strict';\n\nconst emberSourceVersion = require('../utils/ember-source-version');\n\n//------------------------------------------------------------------------------\n// Mapping from tracked-built-ins exports to @ember/reactive/collections exports\n//------------------------------------------------------------------------------\n\nconst TRACKED_BUILT_INS_MAPPING = {\n  TrackedArray: 'trackedArray',\n  TrackedObject: 'trackedObject',\n  TrackedMap: 'trackedMap',\n  TrackedWeakMap: 'trackedWeakMap',\n  TrackedSet: 'trackedSet',\n  TrackedWeakSet: 'trackedWeakSet',\n};\n\nconst TRACKED_BUILT_INS_MODULE = 'tracked-built-ins';\nconst EMBER_REACTIVE_MODULE = '@ember/reactive/collections';\n\nconst ERROR_MESSAGE_IMPORT =\n  'Use imports from `@ember/reactive/collections` instead of `tracked-built-ins`.';\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'enforce usage of `@ember/reactive/collections` imports instead of `tracked-built-ins`',\n      category: 'Ember Octane',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-tracked-built-ins.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      import: ERROR_MESSAGE_IMPORT,\n      newExpression:\n        'Use `{{newName}}(...)` instead of `new {{oldName}}(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n    },\n  },\n\n  ERROR_MESSAGE_IMPORT,\n\n  create(context) {\n    // Only report when ember-source >= 6.8 (which provides @ember/reactive/collections)\n    if (!emberSourceVersion.isEmberSourceVersionAtLeast(6, 8)) {\n      return {};\n    }\n\n    // Track which imported identifiers map to tracked-built-ins classes\n    // so we can fix `new TrackedArray(...)` → `trackedArray(...)`\n    const trackedIdentifiers = new Map();\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value !== TRACKED_BUILT_INS_MODULE) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId: 'import',\n          fix(fixer) {\n            const specifiers = node.specifiers;\n\n            // Only autofix named imports we know how to map\n            const namedSpecifiers = specifiers.filter(\n              (s) => s.type === 'ImportSpecifier' && s.imported.name in TRACKED_BUILT_INS_MAPPING\n            );\n\n            // If there's a default import or unknown named imports, we can't fully autofix\n            const hasDefault = specifiers.some((s) => s.type === 'ImportDefaultSpecifier');\n            const unknownNamed = specifiers.filter(\n              (s) => s.type === 'ImportSpecifier' && !(s.imported.name in TRACKED_BUILT_INS_MAPPING)\n            );\n\n            if (hasDefault || unknownNamed.length > 0 || namedSpecifiers.length === 0) {\n              return null;\n            }\n\n            const newSpecifiers = namedSpecifiers.map((s) => {\n              const newName = TRACKED_BUILT_INS_MAPPING[s.imported.name];\n              if (s.local.name !== s.imported.name) {\n                // Has alias: `import { TrackedArray as TA }` → `import { trackedArray as TA }`\n                return `${newName} as ${s.local.name}`;\n              }\n              return newName;\n            });\n\n            const newImport = `import { ${newSpecifiers.join(', ')} } from '${EMBER_REACTIVE_MODULE}';`;\n            return fixer.replaceText(node, newImport);\n          },\n        });\n\n        // Register the local names for NewExpression tracking\n        for (const specifier of node.specifiers) {\n          if (\n            specifier.type === 'ImportSpecifier' &&\n            specifier.imported.name in TRACKED_BUILT_INS_MAPPING\n          ) {\n            const isAliased = specifier.local.name !== specifier.imported.name;\n            trackedIdentifiers.set(specifier.local.name, {\n              newName: TRACKED_BUILT_INS_MAPPING[specifier.imported.name],\n              isAliased,\n            });\n          }\n        }\n      },\n\n      NewExpression(node) {\n        if (node.callee.type === 'Identifier' && trackedIdentifiers.has(node.callee.name)) {\n          const oldName = node.callee.name;\n          const { newName, isAliased } = trackedIdentifiers.get(oldName);\n\n          context.report({\n            node,\n            messageId: 'newExpression',\n            data: { oldName, newName },\n            fix(fixer) {\n              const sourceCode = context.sourceCode;\n              const newKeyword = sourceCode.getFirstToken(node);\n              const calleeToken = sourceCode.getTokenAfter(newKeyword);\n              const fixes = [\n                // Remove the `new` keyword and any whitespace up to the callee\n                fixer.removeRange([newKeyword.range[0], calleeToken.range[0]]),\n              ];\n              // Only rename the callee if it's not aliased\n              if (!isAliased) {\n                fixes.push(fixer.replaceText(node.callee, newName));\n              }\n              return fixes;\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-tracked-properties-from-args.js",
    "content": "'use strict';\n\nconst { startsWithThisExpression } = require('../utils/utils');\nconst { nodeToDependentKey } = require('../utils/property-getter');\nconst { getImportIdentifier } = require('../utils/import');\nconst { isClassPropertyOrPropertyDefinitionWithDecorator } = require('../utils/decorators');\n\nconst ERROR_MESSAGE =\n  'Do not use this.args to create @tracked properties as this will not be updated if the args change. Instead use a getter.';\n\nfunction visitClassPropertyOrPropertyDefinition(node, context, trackedImportName) {\n  const hasTrackedDecorator =\n    node.decorators?.length > 0 &&\n    isClassPropertyOrPropertyDefinitionWithDecorator(node, trackedImportName);\n\n  const hasThisArgsValue =\n    node.value &&\n    startsWithThisExpression(node.value) &&\n    nodeToDependentKey(node.value, context)?.split('.')[0] === 'args';\n\n  if (hasTrackedDecorator && hasThisArgsValue) {\n    context.report({ node, messageId: 'main' });\n  }\n}\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow creating @tracked properties from this.args',\n      category: 'Ember Octane',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-tracked-properties-from-args.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      main: ERROR_MESSAGE,\n    },\n  },\n  create(context) {\n    let trackedImportName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@glimmer/tracking') {\n          trackedImportName =\n            trackedImportName || getImportIdentifier(node, '@glimmer/tracking', 'tracked');\n        }\n      },\n      ClassProperty(node) {\n        visitClassPropertyOrPropertyDefinition(node, context, trackedImportName);\n      },\n      PropertyDefinition(node) {\n        visitClassPropertyOrPropertyDefinition(node, context, trackedImportName);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-try-invoke.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Use optional chaining operator `?.()` instead of `tryInvoke`';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: \"disallow usage of the Ember's `tryInvoke` util\",\n      category: 'Ember Object',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-try-invoke.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    let importedTryInvokeName;\n\n    return {\n      ImportDeclaration(node) {\n        const importSource = node.source.value;\n\n        if (importSource === '@ember/utils') {\n          importedTryInvokeName =\n            importedTryInvokeName || getImportIdentifier(node, '@ember/utils', 'tryInvoke');\n        }\n      },\n\n      CallExpression(node) {\n        if (types.isIdentifier(node.callee) && node.callee.name === importedTryInvokeName) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-unnecessary-index-route.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE =\n  'The `index` route is automatically provided and does not need to be defined.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary `index` route definition',\n      category: 'Routes',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unnecessary-index-route.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      CallExpression(node) {\n        if (!emberUtils.isRoute(node)) {\n          return;\n        }\n\n        if (node.arguments[0].value !== 'index') {\n          return;\n        }\n\n        context.report({\n          node,\n          message: ERROR_MESSAGE,\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-unnecessary-route-path-option.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst ember = require('../utils/ember');\nconst fixerUtils = require('../utils/fixer');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = 'Do not provide unnecessary `path` option which matches the route name.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary usage of the route `path` option',\n      category: 'Routes',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unnecessary-route-path-option.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (!ember.isRoute(node)) {\n          return;\n        }\n\n        let optionsNode;\n        const hasExplicitPathOption =\n          node.arguments.length >= 2 &&\n          (optionsNode = getNodeOrNodeFromVariable(node.arguments[1], scopeManager)) &&\n          optionsNode.type === 'ObjectExpression' &&\n          hasPropertyWithKeyName(optionsNode, 'path');\n        if (!hasExplicitPathOption) {\n          return;\n        }\n\n        const pathOptionNode = getPropertyByKeyName(optionsNode, 'path');\n        const pathOptionValue = pathOptionNode.value.value;\n        const routeName = node.arguments[0].value;\n\n        if (pathMatchesRouteName(pathOptionValue, routeName)) {\n          context.report({\n            node: pathOptionNode,\n            message: ERROR_MESSAGE,\n            fix(fixer) {\n              const sourceCode = context.sourceCode;\n\n              if (optionsNode.parent.type === 'VariableDeclarator') {\n                // When the options object is a separate variable.\n                return optionsNode.properties.length === 1\n                  ? fixer.remove(pathOptionNode)\n                  : fixerUtils.removeCommaSeparatedNode(pathOptionNode, sourceCode, fixer);\n              }\n\n              // If the `path` option is the only property in the object, remove the entire object.\n              const shouldRemoveObject = optionsNode.properties.length === 1;\n              const nodeToRemove = shouldRemoveObject ? optionsNode : pathOptionNode;\n\n              return fixerUtils.removeCommaSeparatedNode(nodeToRemove, sourceCode, fixer);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n\nfunction hasPropertyWithKeyName(objectExpression, keyName) {\n  return getPropertyByKeyName(objectExpression, keyName) !== undefined;\n}\n\nfunction getPropertyByKeyName(objectExpression, keyName) {\n  return objectExpression.properties.find(\n    (property) =>\n      types.isProperty(property) &&\n      types.isIdentifier(property.key) &&\n      property.key.name === keyName\n  );\n}\n\nfunction pathMatchesRouteName(path, routeName) {\n  if (!path || !routeName) {\n    return false;\n  }\n\n  const pathWithoutOptionalLeadingSlash = path.slice(0, 1) === '/' ? path.slice(1) : path;\n  return pathWithoutOptionalLeadingSlash === routeName;\n}\n"
  },
  {
    "path": "lib/rules/no-unnecessary-service-injection-argument.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE =\n  \"Don't specify injected service name as an argument when it matches the property name.\";\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary argument when injecting services',\n      category: 'Services',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unnecessary-service-injection-argument.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedInjectName;\n    let importedEmberName;\n\n    // Handle either ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).\n    function visitClassPropertyOrPropertyDefinition(node) {\n      if (\n        !emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName) ||\n        node.decorators.length !== 1 ||\n        !types.isCallExpression(node.decorators[0].expression) ||\n        node.decorators[0].expression.arguments.length !== 1 ||\n        !types.isStringLiteral(node.decorators[0].expression.arguments[0])\n      ) {\n        return;\n      }\n\n      const keyName = node.key.name || node.key.value;\n      const firstArg = node.decorators[0].expression.arguments[0];\n      const firstArgValue = firstArg.value;\n      if (keyName === firstArgValue) {\n        context.report({\n          node: firstArg,\n          message: ERROR_MESSAGE,\n          fix(fixer) {\n            return fixer.remove(firstArg);\n          },\n        });\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n      Property(node) {\n        if (\n          !emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName) ||\n          node.value.arguments.length !== 1 ||\n          !types.isStringLiteral(node.value.arguments[0])\n        ) {\n          return;\n        }\n\n        const keyName = node.key.name || node.key.value;\n        const firstArg = node.value.arguments[0];\n        const firstArgValue = firstArg.value;\n        if (keyName === firstArgValue) {\n          context.report({\n            node: firstArg,\n            message: ERROR_MESSAGE,\n            fix(fixer) {\n              return fixer.remove(firstArg);\n            },\n          });\n        }\n      },\n\n      ClassProperty: visitClassPropertyOrPropertyDefinition,\n      PropertyDefinition: visitClassPropertyOrPropertyDefinition,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/no-unused-services.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst propertyGetterUtils = require('../utils/property-getter');\nconst { getImportIdentifier } = require('../utils/import');\nconst {\n  getMacros,\n  MACROS_TO_TRACKED_ARGUMENT_COUNT,\n} = require('../utils/computed-property-macros');\nconst Stack = require('../utils/stack');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unused service injections (see rule doc for limitations)',\n      category: 'Services',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unused-services.md',\n    },\n    fixable: null,\n    hasSuggestions: true,\n    schema: [],\n    messages: {\n      main: 'The service `{{name}}` is not referenced in this file and might be unused (note: it could still be used in a corresponding handlebars template file, mixin, or parent/child class).',\n      removeServiceInjection: 'Remove the service injection.',\n    },\n  },\n\n  create(context) {\n    const classStack = new Stack();\n    const sourceCode = context.sourceCode;\n\n    let importedComputedName;\n    let importedEmberName;\n    let importedGetName;\n    let importedGetPropertiesName;\n    let importedInjectName;\n    let importedObserverName;\n    let importedObservesName;\n    const macros = getMacros();\n    const importedMacros = {};\n\n    /**\n     * Gets the trailing comma token of the given node.\n     * If the trailing comma does not exist, this returns undefined.\n     * @param {ASTNode} node The given node\n     * @returns {Token|undefined} The trailing comma token or undefined\n     */\n    function getTrailingToken(node) {\n      const nextToken = sourceCode.getTokenAfter(node);\n      return types.isCommaToken(nextToken) ? nextToken : undefined;\n    }\n\n    /**\n     * Go through the current class and report any unused services\n     * @returns {void}\n     */\n    function reportInstances() {\n      const currentClass = classStack.pop();\n      const { services, uses } = currentClass;\n      if (Object.keys(services).length === 0) {\n        return;\n      }\n\n      for (const name of Object.keys(services)) {\n        if (!uses.has(name)) {\n          const node = services[name];\n          context.report({\n            node,\n            data: { name },\n            messageId: 'main',\n            suggest: [\n              {\n                messageId: 'removeServiceInjection',\n                fix(fixer) {\n                  const fixers = [fixer.remove(node)];\n                  if (types.isProperty(node)) {\n                    const trailingTokenNode = getTrailingToken(node);\n                    if (trailingTokenNode) {\n                      fixers.push(fixer.remove(trailingTokenNode));\n                    }\n                  }\n                  return fixers;\n                },\n              },\n            ],\n          });\n        }\n      }\n    }\n\n    // Handle either ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).\n    function visitClassPropertyOrPropertyDefinition(node) {\n      // Handles:\n      //   @service(...) foo;\n      // If Ember and Ember.inject weren't imported, skip out early\n      if (!importedEmberName && !importedInjectName) {\n        return;\n      }\n\n      const currentClass = classStack.peek();\n      if (\n        currentClass &&\n        emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)\n      ) {\n        if (node.key.type === 'Identifier') {\n          const name = node.key.name;\n          currentClass.services[name] = node;\n        } else if (types.isStringLiteral(node.key)) {\n          const name = node.key.value;\n          currentClass.services[name] = node;\n        }\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');\n          importedGetPropertiesName =\n            importedGetPropertiesName ||\n            getImportIdentifier(node, '@ember/object', 'getProperties');\n          importedObserverName =\n            importedObserverName || getImportIdentifier(node, '@ember/object', 'observer');\n        }\n        if (node.source.value === '@ember/object/computed') {\n          for (const spec of node.specifiers) {\n            if (spec.type === 'ImportDefaultSpecifier') {\n              continue;\n            }\n            const name = spec.imported.name;\n            if (macros.includes(name)) {\n              const localName = spec.local.name;\n              importedMacros[localName] = name;\n            }\n          }\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n        if (node.source.value === '@ember-decorators/object') {\n          importedObservesName =\n            importedObservesName ||\n            getImportIdentifier(node, '@ember-decorators/object', 'observes');\n        }\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n      },\n      // Native JS class\n      ClassDeclaration(node) {\n        classStack.push({ node, services: {}, uses: new Set() });\n      },\n      // eslint-disable-next-line complexity\n      CallExpression(node) {\n        if (emberUtils.isAnyEmberCoreModule(context, node)) {\n          // Classic class\n          classStack.push({ node, services: {}, uses: new Set() });\n        } else {\n          const currentClass = classStack.peek();\n          if (!currentClass) {\n            return;\n          }\n\n          if (\n            emberUtils.isComputedProp(node, importedEmberName, importedComputedName, {\n              includeMacro: true,\n            })\n          ) {\n            // Ember.computed(), Ember.computed.or(), computed.or()\n            if (types.isMemberExpression(node.callee)) {\n              if (\n                types.isIdentifier(node.callee.object) &&\n                node.callee.object.name === importedEmberName\n              ) {\n                // Ember.computed()\n                for (const elem of node.arguments) {\n                  if (types.isStringLiteral(elem)) {\n                    const name = splitValue(elem.value);\n                    currentClass.uses.add(name);\n                  }\n                }\n              } else if (types.isIdentifier(node.callee.property)) {\n                // Ember.computed.or(), computed.or()\n                const macroName = node.callee.property.name;\n                if (macros.includes(macroName)) {\n                  for (\n                    let idx = 0;\n                    idx < MACROS_TO_TRACKED_ARGUMENT_COUNT[macroName] &&\n                    idx < node.arguments.length;\n                    idx++\n                  ) {\n                    const elem = node.arguments[idx];\n                    if (types.isStringLiteral(elem)) {\n                      const name = splitValue(elem.value);\n                      currentClass.uses.add(name);\n                    }\n                  }\n                }\n              }\n            } else {\n              // computed()\n              for (const elem of node.arguments) {\n                if (types.isStringLiteral(elem)) {\n                  const name = splitValue(elem.value);\n                  currentClass.uses.add(name);\n                }\n              }\n            }\n          } else if (propertyGetterUtils.isThisGetCall(node)) {\n            // this.get('foo...');\n            const name = splitValue(node.arguments[0].value);\n            currentClass.uses.add(name);\n          } else if (\n            types.isThisExpression(node.callee.object) &&\n            node.callee.property.name === 'getProperties'\n          ) {\n            // this.getProperties([..., 'foo..', ...]); or this.getProperties(..., 'foo..', ...);\n            const argArray = types.isArrayExpression(node.arguments[0])\n              ? node.arguments[0].elements\n              : node.arguments;\n            for (const elem of argArray) {\n              const name = splitValue(elem.value);\n              currentClass.uses.add(name);\n            }\n          } else if (types.isIdentifier(node.callee)) {\n            const calleeName = node.callee.name;\n            if (types.isThisExpression(node.arguments[0])) {\n              // If Ember.get and Ember.getProperties weren't imported, skip out early\n              if (!importedGetName && !importedGetPropertiesName) {\n                return;\n              }\n\n              if (calleeName === importedGetName) {\n                // get(this, 'foo...');\n                const name = splitValue(node.arguments[1].value);\n                currentClass.uses.add(name);\n              } else if (calleeName === importedGetPropertiesName) {\n                // getProperties(this, [..., 'foo..', ...]); or getProperties(this, ..., 'foo..', ...);\n                const argArray = types.isArrayExpression(node.arguments[1])\n                  ? node.arguments[1].elements\n                  : node.arguments.slice(1);\n                for (const elem of argArray) {\n                  const name = splitValue(elem.value);\n                  currentClass.uses.add(name);\n                }\n              }\n            } else if (importedMacros[calleeName]) {\n              // Computed macros like @alias(), @or()\n              const macroName = importedMacros[calleeName];\n              for (\n                let idx = 0;\n                idx < MACROS_TO_TRACKED_ARGUMENT_COUNT[macroName] && idx < node.arguments.length;\n                idx++\n              ) {\n                const elem = node.arguments[idx];\n                if (types.isStringLiteral(elem)) {\n                  const name = splitValue(elem.value);\n                  currentClass.uses.add(name);\n                }\n              }\n            } else if (calleeName === importedObserverName) {\n              // observer('foo', ...)\n              for (const elem of node.arguments) {\n                if (types.isStringLiteral(elem)) {\n                  const name = splitValue(elem.value);\n                  currentClass.uses.add(name);\n                }\n              }\n            }\n          } else if (\n            types.isMemberExpression(node.callee) &&\n            types.isIdentifier(node.callee.object) &&\n            node.callee.object.name === importedEmberName &&\n            types.isIdentifier(node.callee.property) &&\n            node.callee.property.name === 'observer'\n          ) {\n            // Ember.observer('foo', ...)\n            for (const elem of node.arguments) {\n              if (types.isStringLiteral(elem)) {\n                const name = splitValue(elem.value);\n                currentClass.uses.add(name);\n              }\n            }\n          }\n        }\n      },\n      'ClassDeclaration:exit'(node) {\n        if (classStack.peek() && classStack.peek().node === node) {\n          // Leaving current (native) class.\n          reportInstances();\n        }\n      },\n      'CallExpression:exit'(node) {\n        if (classStack.peek() && classStack.peek().node === node) {\n          // Leaving current (classic) class.\n          reportInstances();\n        }\n      },\n      // @observes('foo', ...)\n      Decorator(node) {\n        // If Ember and Ember.inject weren't imported OR observes wasn't imported, skip out early\n        if ((!importedEmberName && !importedInjectName) || !importedObservesName) {\n          return;\n        }\n\n        const currentClass = classStack.peek();\n        if (\n          currentClass &&\n          emberUtils.isObserverDecorator(node, importedObservesName) &&\n          types.isCallExpression(node.expression)\n        ) {\n          for (const elem of node.expression.arguments) {\n            if (types.isStringLiteral(elem)) {\n              const name = splitValue(elem.value);\n              currentClass.uses.add(name);\n            }\n          }\n        }\n      },\n      // foo: service(...)\n      Property(node) {\n        // If Ember and Ember.inject weren't imported, skip out early\n        if (!importedEmberName && !importedInjectName) {\n          return;\n        }\n\n        const currentClass = classStack.peek();\n        if (\n          currentClass &&\n          emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)\n        ) {\n          if (node.key.type === 'Identifier') {\n            const name = node.key.name;\n            currentClass.services[name] = node;\n          } else if (types.isStringLiteral(node.key)) {\n            const name = node.key.value;\n            currentClass.services[name] = node;\n          }\n        }\n      },\n\n      ClassProperty: visitClassPropertyOrPropertyDefinition,\n      PropertyDefinition: visitClassPropertyOrPropertyDefinition,\n\n      // this.foo...\n      MemberExpression(node) {\n        const currentClass = classStack.peek();\n        if (\n          currentClass &&\n          types.isThisExpression(node.object) &&\n          types.isIdentifier(node.property)\n        ) {\n          const name = node.property.name;\n          currentClass.uses.add(name);\n        }\n      },\n      GlimmerPathExpression(node) {\n        const currentClass = classStack.peek();\n        if (currentClass && node.head.type === 'ThisHead' && node.tail.length > 0) {\n          const name = node.tail[0];\n          currentClass.uses.add(name);\n        }\n      },\n      VariableDeclarator(node) {\n        const currentClass = classStack.peek();\n        if (\n          currentClass &&\n          node.init &&\n          types.isThisExpression(node.init) &&\n          types.isObjectPattern(node.id)\n        ) {\n          for (const property of node.id.properties) {\n            currentClass.uses.add(property.key.name);\n          }\n        }\n      },\n    };\n  },\n};\n\n/**\n * Splits the value by \".\" and returns the first element\n * @param {String} value The given value\n * @returns {String|undefined} The first split element or undefined if value does not exist\n */\nfunction splitValue(value) {\n  return value ? value.split('.')[0] : undefined;\n}\n"
  },
  {
    "path": "lib/rules/no-volatile-computed-properties.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst ERROR_MESSAGE = 'Do not use volatile computed properties';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow volatile computed properties',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-volatile-computed-properties.md',\n    },\n    schema: [],\n  },\n\n  create(context) {\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      CallExpression(node) {\n        if (\n          types.isMemberExpression(node.callee) &&\n          types.isCallExpression(node.callee.object) &&\n          emberUtils.isComputedProp(node.callee.object, importedEmberName, importedComputedName) &&\n          types.isIdentifier(node.callee.property) &&\n          node.callee.property.name === 'volatile'\n        ) {\n          context.report({ node: node.callee.property, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/order-in-components.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst propOrder = require('../utils/property-order');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst reportUnorderedProperties = propOrder.reportUnorderedProperties;\nconst addBackwardsPosition = propOrder.addBackwardsPosition;\n\nconst ORDER = [\n  'spread',\n  'service',\n  'property',\n  'empty-method',\n  'single-line-function',\n  'multi-line-function',\n  'observer',\n  'init',\n  'didReceiveAttrs',\n  'willRender',\n  'willInsertElement',\n  'didInsertElement',\n  'didRender',\n  'didUpdateAttrs',\n  'willUpdate',\n  'didUpdate',\n  'willDestroyElement',\n  'willClearRender',\n  'didDestroyElement',\n  'actions',\n  'method',\n];\n\n//------------------------------------------------------------------------------\n// Organizing - Organize your components and keep order in objects\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce proper order of properties in components',\n      category: 'Stylistic Issues',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/order-in-components.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          order: {\n            type: 'array',\n            uniqueItems: true,\n            minItems: 1,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    let order = options.order\n      ? addBackwardsPosition(options.order, 'empty-method', 'method')\n      : ORDER;\n    order = [...order];\n    const indexOfLifecycleHook = order.indexOf('lifecycle-hook');\n\n    if (indexOfLifecycleHook !== -1) {\n      order.splice(indexOfLifecycleHook, 1, [\n        'init',\n        'didReceiveAttrs',\n        'willRender',\n        'willInsertElement',\n        'didInsertElement',\n        'didRender',\n        'didUpdateAttrs',\n        'willUpdate',\n        'didUpdate',\n        'willDestroyElement',\n        'willClearRender',\n        'didDestroyElement',\n      ]);\n    }\n\n    let importedInjectName;\n    let importedEmberName;\n    let importedObserverName;\n    let importedControllerName;\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n        if (node.source.value === '@ember/object') {\n          importedObserverName =\n            importedObserverName || getImportIdentifier(node, '@ember/object', 'observer');\n        }\n        if (node.source.value === '@ember/controller') {\n          importedControllerName =\n            importedControllerName || getImportIdentifier(node, '@ember/controller', 'inject');\n        }\n      },\n      CallExpression(node) {\n        if (!ember.isEmberComponent(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'component',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/order-in-controllers.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst propOrder = require('../utils/property-order');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst reportUnorderedProperties = propOrder.reportUnorderedProperties;\nconst addBackwardsPosition = propOrder.addBackwardsPosition;\n\nconst ORDER = [\n  'spread',\n  'controller',\n  'service',\n  'query-params',\n  'inherited-property',\n  'property',\n  'init',\n  'single-line-function',\n  'multi-line-function',\n  'observer',\n  'actions',\n  ['method', 'empty-method'],\n];\n\n//------------------------------------------------------------------------------\n// Organizing - Organize your routes and keep order in objects\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce proper order of properties in controllers',\n      category: 'Stylistic Issues',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/order-in-controllers.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          order: {\n            type: 'array',\n            uniqueItems: true,\n            minItems: 1,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const order = options.order\n      ? addBackwardsPosition(options.order, 'empty-method', 'method')\n      : ORDER;\n\n    let importedInjectName;\n    let importedEmberName;\n    let importedObserverName;\n    let importedControllerName;\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n        if (node.source.value === '@ember/object') {\n          importedObserverName =\n            importedObserverName || getImportIdentifier(node, '@ember/object', 'observer');\n        }\n        if (node.source.value === '@ember/controller') {\n          importedControllerName =\n            importedControllerName || getImportIdentifier(node, '@ember/controller', 'inject');\n        }\n      },\n      CallExpression(node) {\n        if (!ember.isEmberController(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'controller',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n      ClassDeclaration(node) {\n        if (!ember.isEmberController(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'controller',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n      ClassExpression(node) {\n        if (!ember.isEmberController(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'controller',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/order-in-models.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst propOrder = require('../utils/property-order');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst reportUnorderedProperties = propOrder.reportUnorderedProperties;\nconst addBackwardsPosition = propOrder.addBackwardsPosition;\n\nconst ORDER = [\n  'spread',\n  'attribute',\n  'relationship',\n  'single-line-function',\n  'multi-line-function',\n];\n\n//------------------------------------------------------------------------------\n// Organizing - Organize your models\n// Attributes -> Relations -> Computed Properties\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce proper order of properties in models',\n      category: 'Stylistic Issues',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/order-in-models.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          order: {\n            type: 'array',\n            uniqueItems: true,\n            minItems: 1,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const order = options.order\n      ? addBackwardsPosition(options.order, 'empty-method', 'method')\n      : ORDER;\n    const filePath = context.filename;\n\n    let importedInjectName;\n    let importedEmberName;\n    let importedObserverName;\n    let importedControllerName;\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n        if (node.source.value === '@ember/object') {\n          importedObserverName =\n            importedObserverName || getImportIdentifier(node, '@ember/object', 'observer');\n        }\n        if (node.source.value === '@ember/controller') {\n          importedControllerName =\n            importedControllerName || getImportIdentifier(node, '@ember/controller', 'inject');\n        }\n      },\n      CallExpression(node) {\n        if (!ember.isDSModel(node, filePath)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'model',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n      ClassDeclaration(node) {\n        if (!ember.isEmberDataModel(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'model',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n      ClassExpression(node) {\n        if (!ember.isEmberDataModel(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'model',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/order-in-routes.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst propOrder = require('../utils/property-order');\nconst { getImportIdentifier } = require('../utils/import');\n\nconst reportUnorderedProperties = propOrder.reportUnorderedProperties;\nconst addBackwardsPosition = propOrder.addBackwardsPosition;\n\nconst ORDER = [\n  'spread',\n  'service',\n  'inherited-property',\n  'property',\n  'init',\n  'single-line-function',\n  'multi-line-function',\n  'beforeModel',\n  'model',\n  'afterModel',\n  'serialize',\n  'redirect',\n  'activate',\n  'setupController',\n  'renderTemplate',\n  'resetController',\n  'deactivate',\n  'actions',\n  ['method', 'empty-method'],\n];\n\n//------------------------------------------------------------------------------\n// Organizing - Organize your routes and keep order in objects\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce proper order of properties in routes',\n      category: 'Stylistic Issues',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/order-in-routes.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          order: {\n            type: 'array',\n            uniqueItems: true,\n            minItems: 1,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    let order = options.order\n      ? addBackwardsPosition(options.order, 'empty-method', 'method')\n      : ORDER;\n    order = [...order];\n    const indexOfLifecycleHook = order.indexOf('lifecycle-hook');\n\n    if (indexOfLifecycleHook !== -1) {\n      order.splice(indexOfLifecycleHook, 1, [\n        'beforeModel',\n        'afterModel',\n        'serialize',\n        'redirect',\n        'activate',\n        'setupController',\n        'renderTemplate',\n        'resetController',\n        'deactivate',\n      ]);\n    }\n\n    let importedInjectName;\n    let importedEmberName;\n    let importedObserverName;\n    let importedControllerName;\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n        if (node.source.value === '@ember/object') {\n          importedObserverName =\n            importedObserverName || getImportIdentifier(node, '@ember/object', 'observer');\n        }\n        if (node.source.value === '@ember/controller') {\n          importedControllerName =\n            importedControllerName || getImportIdentifier(node, '@ember/controller', 'inject');\n        }\n      },\n      CallExpression(node) {\n        if (!ember.isEmberRoute(context, node)) {\n          return;\n        }\n        reportUnorderedProperties(\n          node,\n          context,\n          'route',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n      ClassDeclaration(node) {\n        if (!ember.isEmberRoute(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'route',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n      ClassExpression(node) {\n        if (!ember.isEmberRoute(context, node)) {\n          return;\n        }\n\n        reportUnorderedProperties(\n          node,\n          context,\n          'route',\n          order,\n          importedEmberName,\n          importedInjectName,\n          importedObserverName,\n          importedControllerName,\n          scopeManager\n        );\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/prefer-ember-test-helpers.js",
    "content": "'use strict';\n\nconst emberUtils = require('../utils/ember');\nconst { ReferenceTracker } = require('eslint-utils');\n\n//-------------------------------------------------------------------------------------\n// Rule Definition\n//-------------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of `@ember/test-helpers` methods over native window methods',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/prefer-ember-test-helpers.md',\n    },\n    schema: [],\n  },\n\n  create: (context) => {\n    if (!emberUtils.isTestFile(context.filename)) {\n      return {};\n    }\n\n    const showErrorMessage = (node, methodName) => {\n      context.report({\n        data: { methodName },\n        message: 'Import the `{{methodName}}()` method from @ember/test-helpers',\n        node,\n      });\n    };\n\n    return {\n      Program(node) {\n        const sourceCode = context.sourceCode;\n        const scope = sourceCode.getScope(node);\n\n        const tracker = new ReferenceTracker(scope);\n        const traceMap = {\n          blur: { [ReferenceTracker.CALL]: true },\n          find: { [ReferenceTracker.CALL]: true },\n          focus: { [ReferenceTracker.CALL]: true },\n        };\n\n        for (const { node } of tracker.iterateGlobalReferences(traceMap)) {\n          showErrorMessage(node, node.callee.name);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-async-inverse-relationship.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require inverse to be specified in @belongsTo and @hasMany decorators',\n      category: 'Ember Data',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-async-inverse-relationship.md',\n    },\n    schema: [],\n  },\n\n  create(context) {\n    return {\n      CallExpression(node) {\n        const decorator =\n          node.parent.type === 'Decorator' &&\n          ['belongsTo', 'hasMany'].includes(node.callee.name) &&\n          node;\n\n        if (decorator) {\n          const args = decorator.arguments;\n          const hasAsync = args.some(\n            (arg) =>\n              arg.type === 'ObjectExpression' &&\n              arg.properties.some((prop) => prop.key.name === 'async')\n          );\n          const hasBooleanAsync = args.some(\n            (arg) =>\n              arg.type === 'ObjectExpression' &&\n              arg.properties.some(\n                (prop) => prop.key.name === 'async' && typeof prop.value.value === 'boolean'\n              )\n          );\n          const hasInverse = args.some(\n            (arg) =>\n              arg.type === 'ObjectExpression' &&\n              arg.properties.some((prop) => prop.key.name === 'inverse')\n          );\n\n          if (!hasAsync) {\n            context.report({\n              node,\n              message: 'The @{{decorator}} decorator requires an `async` property to be specified.',\n              data: {\n                decorator: decorator.callee.name,\n              },\n            });\n          } else if (!hasBooleanAsync) {\n            context.report({\n              node,\n              message:\n                'The @{{decorator}} decorator requires an `async` property to be specified as a boolean.',\n              data: {\n                decorator: decorator.callee.name,\n              },\n            });\n          }\n\n          if (!hasInverse) {\n            context.report({\n              node,\n              message:\n                'The @{{decorator}} decorator requires an `inverse` property to be specified.',\n              data: {\n                decorator: decorator.callee.name,\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-computed-macros.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst emberUtils = require('../utils/ember');\nconst propertyGetterUtils = require('../utils/property-getter');\nconst assert = require('assert');\nconst scopeReferencesThis = require('../utils/scope-references-this');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nfunction makeErrorMessage(macro) {\n  return `Use the \\`${macro}\\` computed property macro.`;\n}\n\nconst ERROR_MESSAGE_READS = makeErrorMessage('reads');\nconst ERROR_MESSAGE_AND = makeErrorMessage('and');\nconst ERROR_MESSAGE_OR = makeErrorMessage('or');\nconst ERROR_MESSAGE_GT = makeErrorMessage('gt');\nconst ERROR_MESSAGE_GTE = makeErrorMessage('gte');\nconst ERROR_MESSAGE_LT = makeErrorMessage('lt');\nconst ERROR_MESSAGE_LTE = makeErrorMessage('lte');\nconst ERROR_MESSAGE_NOT = makeErrorMessage('not');\nconst ERROR_MESSAGE_EQUAL = makeErrorMessage('equal');\nconst ERROR_MESSAGE_FILTER_BY = makeErrorMessage('filterBy');\nconst ERROR_MESSAGE_MAP_BY = makeErrorMessage('mapBy');\n\n/**\n * Checks if a CallExpression node looks like `this.x.someFunction()` or `this.x.y.someFunction()`.\n *\n * @param {Node} node The CallExpression node to check.\n * @param {String} functionName The name of the function call to check for.\n * @returns {boolean} Whether the node looks like `this.x.someFunction()` or `this.x.y.someFunction()`.\n */\nfunction isThisPropertyFunctionCall(node, functionName) {\n  if (\n    node.type !== 'CallExpression' ||\n    node.callee.type !== 'MemberExpression' ||\n    node.callee.property.type !== 'Identifier' ||\n    node.callee.property.name !== functionName\n  ) {\n    return false;\n  }\n\n  let current = node.callee;\n  while (current !== null) {\n    if (\n      types.isMemberExpression(current) &&\n      !current.computed &&\n      types.isIdentifier(current.property)\n    ) {\n      current = current.object;\n    } else if (types.isThisExpression(current)) {\n      return true;\n    } else {\n      break;\n    }\n  }\n\n  return false;\n}\n\n/**\n * Checks if a LogicalExpression looks like `this.x && this.y && this.z`\n * containing only simple `this.x` MemberExpression or `this.get('x')` CallExpression nodes.\n *\n * @param {Node} node The LogicalExpression node to check.\n * @returns {boolean} Whether the node looks like `this.x && this.y && this.z` or `this.get('x') && this.get('y') && this.get('z')`.\n */\nfunction isSimpleThisExpressionsInsideLogicalExpression(node, operator) {\n  assert(types.isLogicalExpression(node), 'Must call function on LogicalExpression');\n\n  let current = node;\n  while (current !== null) {\n    if (types.isLogicalExpression(current)) {\n      if (!propertyGetterUtils.isSimpleThisExpression(current.right)) {\n        return false;\n      }\n      if (current.operator !== operator) {\n        return false;\n      }\n      current = current.left;\n    } else if (propertyGetterUtils.isSimpleThisExpression(current)) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n  return false;\n}\n\n/**\n * Converts a simple LogicalExpression into an array of the MemberExpression/CallExpression nodes in it.\n *\n * Example Input:   A LogicalExpression node with this source: `this.x && this.y.z && this.w`\n * Example Output:  An array of these MemberExpression nodes: [this.x, this.y.z, this.w]\n *\n * @param {Node} node The LogicalExpression node containing nodes that look like `this.x` or `this.get('x')`.\n * @returns {Node[]} An array of MemberExpression/CallExpression nodes contained in the LogicalExpression.\n */\nfunction getThisExpressions(nodeLogicalExpression) {\n  const arrayOfThisExpressions = [];\n  let current = nodeLogicalExpression;\n  while (current !== null) {\n    if (types.isLogicalExpression(current)) {\n      arrayOfThisExpressions.push(current.right);\n      current = current.left;\n    } else {\n      arrayOfThisExpressions.push(current);\n      break;\n    }\n  }\n  return arrayOfThisExpressions.reverse();\n}\n\nfunction getPropertyName(nodeComputedProperty) {\n  // Classic: `storeIds: computed(...)` → parent is Property with key.name\n  // Decorator: `@computed() get storeIds()` → nodeComputedProperty is MethodDefinition with key.name\n  return nodeComputedProperty.parent?.key?.name ?? nodeComputedProperty.key?.name;\n}\n\nfunction makeFix(nodeToReplace, macro, macroArgs) {\n  const isDecoratorUsage = types.isMethodDefinition(nodeToReplace);\n  const prefix = isDecoratorUsage ? '@' : ''; // Decorator usage has @ symbol prefixing computed()\n  const suffix = isDecoratorUsage ? ` ${nodeToReplace.key.name}` : ''; // Decorator usage has property name as suffix.\n  return `${prefix}computed.${macro}(${macroArgs})${suffix}`;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require using computed property macros when possible',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-computed-macros.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          includeNativeGetters: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Whether the rule should check and autofix computed properties with native getters (i.e. `@computed() get someProp() {}`) to use computed property macros. This is off by default because in the Ember Octane world, the better improvement would be to keep the native getter and use tracked properties instead of computed properties.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  ERROR_MESSAGE_READS,\n  ERROR_MESSAGE_AND,\n  ERROR_MESSAGE_OR,\n  ERROR_MESSAGE_GT,\n  ERROR_MESSAGE_GTE,\n  ERROR_MESSAGE_LT,\n  ERROR_MESSAGE_LTE,\n  ERROR_MESSAGE_NOT,\n  ERROR_MESSAGE_EQUAL,\n  ERROR_MESSAGE_FILTER_BY,\n  ERROR_MESSAGE_MAP_BY,\n\n  create(context) {\n    function reportSingleArg(nodeComputedProperty, nodeWithThisExpression, macro) {\n      const text = propertyGetterUtils.nodeToDependentKey(nodeWithThisExpression, context);\n      const propertyName = getPropertyName(nodeComputedProperty);\n      const isSelfReferential = text === propertyName;\n\n      context.report({\n        node: nodeComputedProperty,\n        message: makeErrorMessage(macro),\n        fix: isSelfReferential\n          ? undefined\n          : (fixer) => {\n              return fixer.replaceText(\n                nodeComputedProperty,\n                makeFix(nodeComputedProperty, macro, `'${text}'`)\n              );\n            },\n      });\n    }\n\n    function reportBinaryExpression(nodeComputedProperty, nodeBinaryExpression, macro) {\n      context.report({\n        node: nodeComputedProperty,\n        message: makeErrorMessage(macro),\n        fix(fixer) {\n          const sourceCode = context.sourceCode;\n          const textLeft = propertyGetterUtils.nodeToDependentKey(\n            nodeBinaryExpression.left,\n            context\n          );\n          const textRight = sourceCode.getText(nodeBinaryExpression.right);\n          return fixer.replaceText(\n            nodeComputedProperty,\n            makeFix(nodeComputedProperty, macro, `'${textLeft}', ${textRight}`)\n          );\n        },\n      });\n    }\n\n    function reportLogicalExpression(nodeComputedProperty, nodeLogicalExpression, macro) {\n      context.report({\n        node: nodeComputedProperty,\n        message: makeErrorMessage(macro),\n        fix(fixer) {\n          const text = getThisExpressions(nodeLogicalExpression)\n            .map((node) => propertyGetterUtils.nodeToDependentKey(node, context))\n            .join(\"', '\");\n          return fixer.replaceText(\n            nodeComputedProperty,\n            makeFix(nodeComputedProperty, macro, `'${text}'`)\n          );\n        },\n      });\n    }\n\n    function reportFunctionCall(nodeComputedProperty, nodeCallExpression, macro) {\n      context.report({\n        node: nodeComputedProperty,\n        message: makeErrorMessage(macro),\n        fix(fixer) {\n          const sourceCode = context.sourceCode;\n          const arg1 = propertyGetterUtils.nodeToDependentKey(\n            nodeCallExpression.callee.object,\n            context\n          );\n          const restOfArgs = nodeCallExpression.arguments.map((arg) => sourceCode.getText(arg));\n          return fixer.replaceText(\n            nodeComputedProperty,\n            makeFix(nodeComputedProperty, macro, `'${arg1}', ${restOfArgs.join(', ')}`)\n          );\n        },\n      });\n    }\n\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      // eslint-disable-next-line complexity\n      CallExpression(node) {\n        if (!emberUtils.isComputedProp(node, importedEmberName, importedComputedName)) {\n          return;\n        }\n\n        // Options:\n        const includeNativeGetters = context.options[0] && context.options[0].includeNativeGetters;\n\n        let getterBody;\n        let nodeToReport;\n        if (node.arguments.length > 0 && types.isFunctionExpression(node.arguments.at(-1))) {\n          // Example: computed('dependentKey', function() { return this.x })\n          getterBody = node.arguments.at(-1).body.body;\n          nodeToReport = node;\n        } else if (\n          includeNativeGetters &&\n          types.isDecorator(node.parent) &&\n          types.isMethodDefinition(node.parent.parent) &&\n          node.parent.parent.decorators.length === 1 &&\n          node.parent.parent.kind === 'get' &&\n          types.isFunctionExpression(node.parent.parent.value)\n        ) {\n          // Example: @computed() get someProp() { return this.x; }\n          getterBody = node.parent.parent.value.body.body;\n          nodeToReport = node.parent.parent;\n        } else {\n          return;\n        }\n\n        if (getterBody.length !== 1) {\n          return;\n        }\n\n        if (!types.isReturnStatement(getterBody[0])) {\n          return;\n        }\n\n        const statement = getterBody[0].argument;\n        if (!statement) {\n          return;\n        }\n\n        if (\n          types.isUnaryExpression(statement) &&\n          statement.operator === '!' &&\n          propertyGetterUtils.isSimpleThisExpression(statement.argument)\n        ) {\n          reportSingleArg(nodeToReport, statement.argument, 'not');\n        } else if (types.isLogicalExpression(statement)) {\n          if (isSimpleThisExpressionsInsideLogicalExpression(statement, '&&')) {\n            reportLogicalExpression(nodeToReport, statement, 'and');\n          } else if (isSimpleThisExpressionsInsideLogicalExpression(statement, '||')) {\n            reportLogicalExpression(nodeToReport, statement, 'or');\n          }\n        } else if (\n          types.isBinaryExpression(statement) &&\n          types.isLiteral(statement.right) &&\n          propertyGetterUtils.isSimpleThisExpression(statement.left)\n        ) {\n          switch (statement.operator) {\n            case '===': {\n              reportBinaryExpression(nodeToReport, statement, 'equal');\n\n              break;\n            }\n            case '>': {\n              reportBinaryExpression(nodeToReport, statement, 'gt');\n\n              break;\n            }\n            case '>=': {\n              reportBinaryExpression(nodeToReport, statement, 'gte');\n\n              break;\n            }\n            case '<': {\n              reportBinaryExpression(nodeToReport, statement, 'lt');\n\n              break;\n            }\n            case '<=': {\n              reportBinaryExpression(nodeToReport, statement, 'lte');\n\n              break;\n            }\n            // No default\n          }\n        } else if (propertyGetterUtils.isSimpleThisExpression(statement)) {\n          reportSingleArg(nodeToReport, statement, 'reads');\n        } else if (\n          isThisPropertyFunctionCall(statement, 'filterBy') &&\n          !statement.arguments.some(scopeReferencesThis)\n        ) {\n          reportFunctionCall(nodeToReport, statement, 'filterBy');\n        } else if (\n          isThisPropertyFunctionCall(statement, 'mapBy') &&\n          !statement.arguments.some(scopeReferencesThis)\n        ) {\n          reportFunctionCall(nodeToReport, statement, 'mapBy');\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-computed-property-dependencies.js",
    "content": "'use strict';\n\nconst estraverse = require('estraverse');\nconst emberUtils = require('../utils/ember');\nconst computedPropertyUtils = require('../utils/computed-properties');\nconst types = require('../utils/types');\nconst utils = require('../utils/utils');\nconst propertyGetterUtils = require('../utils/property-getter');\nconst computedPropertyDependentKeyUtils = require('../utils/computed-property-dependent-keys');\nconst assert = require('assert');\nconst { getImportIdentifier } = require('../utils/import');\n\n/**\n * Checks whether the node is an identifier with the given name.\n *\n * @param {ASTNode} node\n * @param {string} name\n * @returns {boolean}\n */\nfunction isIdentifier(node, name) {\n  if (!types.isIdentifier(node)) {\n    return false;\n  }\n\n  return node.name === name;\n}\n\n/**\n * Determines whether a node is a simple member expression with the given object\n * and property.\n *\n * @param {ASTNode} node\n * @param {string} objectName\n * @param {string} propertyName\n * @returns {boolean}\n */\nfunction isMemberExpression(node, objectName, propertyName) {\n  return (\n    node &&\n    types.isMemberExpression(node) &&\n    !node.computed &&\n    (objectName === 'this'\n      ? types.isThisExpression(node.object)\n      : isIdentifier(node.object, objectName)) &&\n    isIdentifier(node.property, propertyName)\n  );\n}\n\n/**\n * Checks if a node looks like: 'part1' + 'part2'\n *\n * @param {ASTNode} node\n * @returns {boolean}\n */\nfunction isTwoPartStringLiteral(node) {\n  return (\n    types.isBinaryExpression(node) &&\n    types.isStringLiteral(node.left) &&\n    types.isStringLiteral(node.right)\n  );\n}\n\n/**\n * Returns the string represented by the node.\n *\n * @param {ASTNode} node\n * @returns {string}\n */\nfunction nodeToStringValue(node) {\n  if (types.isStringLiteral(node)) {\n    return node.value;\n  } else if (isTwoPartStringLiteral(node)) {\n    return node.left.value + node.right.value;\n  } else {\n    assert(false);\n    return undefined;\n  }\n}\n\n/**\n * Splits arguments to `Ember.computed` into string keys and dynamic keys.\n *\n * @param {Array<ASTNode>} args\n * @returns {{keys: Array<ASTNode>, dynamicKeys: Array<ASTNode>}}\n */\nfunction parseComputedDependencies(args) {\n  const keys = [];\n  const dynamicKeys = [];\n\n  for (const arg of args) {\n    if (types.isStringLiteral(arg) || isTwoPartStringLiteral(arg)) {\n      keys.push(arg);\n    } else if (!computedPropertyUtils.isComputedPropertyBodyArg(arg)) {\n      dynamicKeys.push(arg);\n    }\n  }\n\n  return { keys, dynamicKeys };\n}\n\n/**\n * Recursively finds all calls to `Ember#get`, whether like `Ember.get(this, …)`\n * or `this.get(…)`.\n *\n * @param {ASTNode} node\n * @param {object} importedNames\n * @returns {Array<ASTNode>}\n */\nfunction findEmberGetCalls(node, importedNames) {\n  const results = [];\n\n  estraverse.traverse(node, {\n    enter(child) {\n      if (types.isCallExpression(child)) {\n        const dependency = extractEmberGetDependencies(child, importedNames);\n\n        if (dependency.length > 0) {\n          results.push(child);\n        }\n      }\n    },\n    fallback: 'iteration',\n  });\n\n  return results;\n}\n\n/**\n * Recursively finds all `this.property` usages.\n *\n * @param {ASTNode} node\n * @returns {Array<ASTNode>}\n */\nfunction findThisGetCalls(node) {\n  const results = [];\n\n  estraverse.traverse(node, {\n    enter(child, parent) {\n      if (\n        (types.isOptionalMemberExpression(child) || types.isMemberExpression(child)) &&\n        !(\n          (types.isCallExpression(parent) || types.isOptionalCallExpression(parent)) &&\n          parent.callee === child\n        ) &&\n        !utils.isInLeftSideOfAssignmentExpression(child) && // Ignore the left side (x) of an assignment: this.x = 123;\n        propertyGetterUtils.isSimpleThisExpression(child)\n      ) {\n        results.push(child);\n      }\n    },\n    fallback: 'iteration',\n  });\n\n  return results;\n}\n\n/**\n * Get an array argument's elements or the rest params if the values were not\n * passed as a single array argument.\n *\n * @param {Array<ASTNode>} args\n * @returns {Array<ASTNode>}\n */\nfunction getArrayOrRest(args) {\n  if (args.length === 1 && types.isArrayExpression(args[0])) {\n    return args[0].elements;\n  }\n  return args;\n}\n\n/**\n * Extracts all static property keys used in the various forms of `Ember.get`.\n *\n * @param {ASTNode} call\n * @param {object} importedNames\n * @returns {Array<string>}\n */\nfunction extractEmberGetDependencies(\n  call,\n  { importedEmberName, importedGetName, importedGetPropertiesName, importedGetWithDefaultName }\n) {\n  if (\n    isMemberExpression(call.callee, 'this', 'get') ||\n    isMemberExpression(call.callee, 'this', 'getWithDefault')\n  ) {\n    const firstArg = call.arguments[0];\n\n    if (types.isStringLiteral(firstArg)) {\n      return [firstArg.value];\n    }\n  } else if (\n    isMemberExpression(call.callee, importedEmberName, 'get') ||\n    isMemberExpression(call.callee, importedEmberName, 'getWithDefault') ||\n    isIdentifier(call.callee, importedGetName) ||\n    isIdentifier(call.callee, importedGetWithDefaultName)\n  ) {\n    const firstArg = call.arguments[0];\n    const secondArgument = call.arguments[1];\n\n    if (types.isThisExpression(firstArg) && types.isStringLiteral(secondArgument)) {\n      return [secondArgument.value];\n    }\n  } else if (isMemberExpression(call.callee, 'this', 'getProperties')) {\n    return getArrayOrRest(call.arguments)\n      .filter(types.isStringLiteral)\n      .map((arg) => arg.value);\n  } else if (\n    isMemberExpression(call.callee, importedEmberName, 'getProperties') ||\n    isIdentifier(call.callee, importedGetPropertiesName)\n  ) {\n    const firstArg = call.arguments[0];\n    const rest = call.arguments.slice(1);\n\n    if (types.isThisExpression(firstArg)) {\n      return getArrayOrRest(rest)\n        .filter(types.isStringLiteral)\n        .map((arg) => arg.value);\n    }\n  }\n\n  return [];\n}\n\nfunction extractThisGetDependencies(memberExpression, context) {\n  return propertyGetterUtils.nodeToDependentKey(memberExpression, context);\n}\n\nfunction removeRedundantKeys(keys) {\n  return keys.filter(\n    (currentKey) => !computedPropertyDependentKeyUtils.keyExistsAsPrefixInList(keys, currentKey)\n  );\n}\n\nfunction removeServiceNames(keys, serviceNames) {\n  if (!serviceNames || serviceNames.length === 0) {\n    return keys;\n  }\n  return keys.filter((key) => !serviceNames.includes(key));\n}\n\nconst ERROR_MESSAGE_NON_STRING_VALUE = 'Non-string value used as computed property dependency';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require dependencies to be declared statically in computed properties',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-computed-property-dependencies.md',\n    },\n\n    fixable: 'code',\n\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowDynamicKeys: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should allow or disallow dynamic (variable / non-string) dependency keys in computed properties.',\n          },\n          requireServiceNames: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Whether the rule should require injected service names to be listed as dependency keys in computed properties.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  ERROR_MESSAGE_NON_STRING_VALUE,\n\n  create(context) {\n    // Options:\n    const requireServiceNames = context.options[0] && context.options[0].requireServiceNames;\n    const allowDynamicKeys = !context.options[0] || context.options[0].allowDynamicKeys;\n\n    const serviceNames = [];\n\n    let importedEmberName;\n    let importedComputedName;\n    let importedGetName;\n    let importedGetPropertiesName;\n    let importedGetWithDefaultName;\n    let importedInjectName;\n\n    function checkComputedDependencies(node, nodeArguments, importedNames) {\n      const declaredDependencies = parseComputedDependencies(nodeArguments);\n\n      if (!allowDynamicKeys) {\n        for (const key of declaredDependencies.dynamicKeys) {\n          context.report({\n            node: key,\n            message: ERROR_MESSAGE_NON_STRING_VALUE,\n          });\n        }\n      }\n\n      const computedPropertyFunctionBody =\n        computedPropertyUtils.getComputedPropertyFunctionBody(node);\n\n      const usedKeys1 = findEmberGetCalls(computedPropertyFunctionBody, importedNames).flatMap(\n        (node) => extractEmberGetDependencies(node, importedNames)\n      );\n\n      const usedKeys2 = findThisGetCalls(computedPropertyFunctionBody).flatMap((node) =>\n        extractThisGetDependencies(node, context)\n      );\n      const usedKeys = [...usedKeys1, ...usedKeys2];\n\n      const expandedDeclaredKeys = computedPropertyDependentKeyUtils.expandKeys(\n        declaredDependencies.keys.map(nodeToStringValue)\n      );\n\n      const undeclaredKeysBeforeServiceCheck = removeRedundantKeys(\n        usedKeys\n          .filter((usedKey) =>\n            expandedDeclaredKeys.every(\n              (declaredKey) =>\n                declaredKey !== usedKey &&\n                !computedPropertyDependentKeyUtils.computedPropertyDependencyMatchesKeyPath(\n                  declaredKey,\n                  usedKey\n                )\n            )\n          )\n          .reduce((keys, key) => {\n            if (!keys.includes(key)) {\n              keys.push(key);\n            }\n            return keys;\n          }, [])\n          .sort()\n      );\n\n      const undeclaredKeys = requireServiceNames\n        ? undeclaredKeysBeforeServiceCheck\n        : removeServiceNames(undeclaredKeysBeforeServiceCheck, serviceNames);\n\n      if (undeclaredKeys.length > 0) {\n        context.report({\n          node,\n          message: 'Use of undeclared dependencies in computed property: {{undeclaredKeys}}',\n          data: { undeclaredKeys: undeclaredKeys.join(', ') },\n          fix(fixer) {\n            const sourceCode = context.sourceCode;\n\n            const missingDependenciesAsArgumentsForDynamicKeys =\n              declaredDependencies.dynamicKeys.map((dynamicKey) => sourceCode.getText(dynamicKey));\n            const missingDependenciesAsArgumentsForStringKeys =\n              computedPropertyDependentKeyUtils.collapseKeys(\n                removeRedundantKeys([...undeclaredKeys, ...expandedDeclaredKeys])\n              );\n\n            const missingDependenciesAsArguments = [\n              ...missingDependenciesAsArgumentsForDynamicKeys,\n              ...missingDependenciesAsArgumentsForStringKeys,\n            ].join(', ');\n\n            if (nodeArguments.length > 0) {\n              const lastArg = node.arguments.at(-1);\n              if (computedPropertyUtils.isComputedPropertyBodyArg(lastArg)) {\n                if (node.arguments.length > 1) {\n                  const firstDependency = node.arguments[0];\n                  const lastDependency = node.arguments.at(-2);\n\n                  // Replace the dependent keys before the function body argument.\n                  // Before: computed('first', function() {})\n                  // After: computed('first', 'last', function() {})\n                  return fixer.replaceTextRange(\n                    [firstDependency.range[0], lastDependency.range[1]],\n                    missingDependenciesAsArguments\n                  );\n                } else {\n                  // Add dependent keys before the function body argument.\n                  // Before: computed(function() {})\n                  // After: computed('key', function() {})\n                  return fixer.insertTextBefore(lastArg, `${missingDependenciesAsArguments}, `);\n                }\n              } else {\n                // All arguments are dependent keys, so replace them all.\n                // Before: @computed('first')\n                // After: @computed('first', 'last')\n                const firstDependency = nodeArguments[0];\n                const lastDependency = lastArg;\n                return fixer.replaceTextRange(\n                  [firstDependency.range[0], lastDependency.range[1]],\n                  missingDependenciesAsArguments\n                );\n              }\n            } else {\n              const nodeText = sourceCode.getText(node);\n              if (types.isCallExpression(node)) {\n                // Insert dependencies inside empty parenthesis.\n                // Before: @computed()\n                // After: @computed('first')\n                const positionAfterParenthesis = node.range[0] + nodeText.indexOf('(') + 1;\n                return fixer.insertTextAfterRange(\n                  [node.range[0], positionAfterParenthesis],\n                  missingDependenciesAsArguments\n                );\n              } else {\n                // Add dependencies with parentheses.\n                // Before: @computed\n                // After: @computed('first')\n                return fixer.insertTextAfterRange(\n                  [node.range[0], node.range[0] + nodeText.length],\n                  `(${missingDependenciesAsArguments})`\n                );\n              }\n            }\n          },\n        });\n      }\n    }\n\n    function collectServiceNames(node) {\n      // If service names aren't required dependencies, then we need to keep track of them so that we can ignore them.\n      if (\n        !requireServiceNames &&\n        emberUtils.isInjectedServiceProp(node, importedEmberName, importedInjectName)\n      ) {\n        if (types.isIdentifier(node.key)) {\n          serviceNames.push(node.key.name);\n        } else if (types.isStringLiteral(node.key)) {\n          serviceNames.push(node.key.value);\n        }\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');\n          importedGetPropertiesName =\n            importedGetPropertiesName ||\n            getImportIdentifier(node, '@ember/object', 'getProperties');\n          importedGetWithDefaultName =\n            importedGetWithDefaultName ||\n            getImportIdentifier(node, '@ember/object', 'getWithDefault');\n        }\n        if (node.source.value === '@ember/service') {\n          importedInjectName =\n            importedInjectName ||\n            getImportIdentifier(node, '@ember/service', 'inject') ||\n            getImportIdentifier(node, '@ember/service', 'service');\n        }\n      },\n\n      Identifier(node) {\n        if (emberUtils.isComputedProp(node, importedEmberName, importedComputedName)) {\n          checkComputedDependencies(node, [], {\n            importedEmberName,\n            importedGetName,\n            importedGetPropertiesName,\n            importedGetWithDefaultName,\n          });\n        }\n      },\n\n      CallExpression(node) {\n        if (emberUtils.isComputedProp(node, importedEmberName, importedComputedName)) {\n          checkComputedDependencies(node, node.arguments, {\n            importedEmberName,\n            importedGetName,\n            importedGetPropertiesName,\n            importedGetWithDefaultName,\n          });\n        }\n      },\n\n      ClassBody(node) {\n        for (const bodyNode of node.body) {\n          collectServiceNames(bodyNode);\n        }\n      },\n\n      ObjectExpression(node) {\n        for (const property of node.properties) {\n          collectServiceNames(property);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-fetch-import.js",
    "content": "'use strict';\n\nconst { ReferenceTracker } = require('eslint-utils');\n\nconst ERROR_MESSAGE = 'Explicitly import `fetch` instead of using `window.fetch`';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce explicit import for `fetch()`',\n      category: 'Miscellaneous',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-fetch-import.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    return {\n      'Program:exit'(node) {\n        const sourceCode = context.sourceCode;\n        const scope = sourceCode.getScope(node);\n\n        const tracker = new ReferenceTracker(scope);\n\n        const traceMap = {\n          fetch: { [ReferenceTracker.CALL]: true },\n        };\n\n        for (const { node } of tracker.iterateGlobalReferences(traceMap)) {\n          context.report({ node, message: ERROR_MESSAGE });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-return-from-computed.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// General rule - Always return a value from computed properties\n//------------------------------------------------------------------------------\n\nfunction isAnySegmentReachable(segments) {\n  for (const segment of segments) {\n    if (segment.reachable) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nconst ERROR_MESSAGE = 'Always return a value from computed properties';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow missing return statements in computed properties',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-return-from-computed.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const report = function (node) {\n      context.report({ node, message: ERROR_MESSAGE });\n    };\n\n    let funcInfo = {\n      upper: null,\n      codePath: null,\n      shouldCheck: false,\n      node: null,\n      currentSegments: [],\n    };\n\n    function checkLastSegment(node) {\n      if (funcInfo.shouldCheck && isAnySegmentReachable(funcInfo.currentSegments)) {\n        report(node);\n      }\n    }\n\n    let importedEmberName;\n    let importedComputedName;\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      onCodePathStart(codePath, node) {\n        const sourceCode = context.sourceCode;\n        const ancestors = sourceCode.getAncestors\n          ? sourceCode.getAncestors(node)\n          : context.getAncestors();\n\n        funcInfo = {\n          upper: funcInfo,\n          codePath,\n          shouldCheck:\n            ancestors.findIndex((node) =>\n              ember.isComputedProp(node, importedEmberName, importedComputedName)\n            ) > -1,\n          node,\n          currentSegments: new Set(),\n        };\n      },\n\n      // Pops this function's information.\n      onCodePathEnd() {\n        funcInfo = funcInfo.upper;\n      },\n      onUnreachableCodePathSegmentStart(segment) {\n        funcInfo.currentSegments.add(segment);\n      },\n\n      onUnreachableCodePathSegmentEnd(segment) {\n        funcInfo.currentSegments.delete(segment);\n      },\n\n      onCodePathSegmentStart(segment) {\n        funcInfo.currentSegments.add(segment);\n      },\n\n      onCodePathSegmentEnd(segment) {\n        funcInfo.currentSegments.delete(segment);\n      },\n\n      'FunctionExpression:exit'(node) {\n        if (node.parent.parent.parent === null) {\n          return;\n        }\n\n        if (\n          ember.isComputedProp(node.parent, importedEmberName, importedComputedName) ||\n          ember.isComputedProp(node.parent.parent.parent, importedEmberName, importedComputedName)\n        ) {\n          checkLastSegment(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-super-in-lifecycle-hooks.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst types = require('../utils/types');\nconst estraverse = require('estraverse');\n\nfunction hasMatchingNode(node, matcher) {\n  let foundMatch = false;\n  estraverse.traverse(node, {\n    enter(child) {\n      if (!foundMatch && matcher(child)) {\n        foundMatch = true;\n      }\n    },\n    fallback: 'iteration',\n  });\n  return foundMatch;\n}\n\n/**\n * Checks for this._super() call.\n * @param {node} node\n * @returns {Boolean}\n */\nfunction isClassicSuper(node) {\n  return (\n    types.isCallExpression(node) &&\n    types.isMemberExpression(node.callee) &&\n    types.isThisExpression(node.callee.object) &&\n    types.isIdentifier(node.callee.property) &&\n    node.callee.property.name === '_super'\n  );\n}\n\n/**\n * Checks for a call like super.init() or super.didInsertElement().\n * @param {node} node\n * @param {string} hook - name of hook\n * @returns {Boolean}\n */\nfunction isNativeSuper(node, hook) {\n  return (\n    types.isCallExpression(node) &&\n    types.isMemberExpression(node.callee) &&\n    node.callee.object.type === 'Super' &&\n    types.isIdentifier(node.callee.property) &&\n    node.callee.property.name === hook\n  );\n}\n\n//----------------------------------------------\n// General rule - Call super in lifecycle hooks\n//----------------------------------------------\n\nconst ERROR_MESSAGE = 'Call super in lifecycle hooks';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE,\n\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require super to be called in lifecycle hooks',\n      category: 'Ember Object',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-super-in-lifecycle-hooks.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          checkInitOnly: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Whether the rule should only check the `init` lifecycle hook and not other lifecycle hooks.',\n          },\n          checkNativeClasses: {\n            type: 'boolean',\n            default: true,\n            description:\n              'Whether the rule should check lifecycle hooks in native classes (in addition to classic classes).',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    const checkInitOnly = context.options[0] && context.options[0].checkInitOnly;\n    const checkNativeClasses = !context.options[0] || context.options[0].checkNativeClasses;\n\n    function report(node, isNativeClass, lifecycleHookName) {\n      context.report({\n        node,\n        message: ERROR_MESSAGE,\n        fix(fixer) {\n          // attrs hooks should call super without ...arguments to satisfy ember/no-attrs-snapshot rule.\n          const replacementArgs = ['didReceiveAttrs', 'didUpdateAttrs'].includes(lifecycleHookName)\n            ? ''\n            : '...arguments';\n\n          const replacement = isNativeClass\n            ? `super.${lifecycleHookName}(${replacementArgs});`\n            : `this._super(${replacementArgs});`;\n          // Insert right after function curly brace.\n          const sourceCode = context.sourceCode;\n          const startOfBlockStatement = sourceCode.getFirstToken(node.value.body);\n          return fixer.insertTextAfter(startOfBlockStatement, `\\n${replacement}`);\n        },\n      });\n    }\n\n    function isLifecycleHook(node, isGlimmerComponent) {\n      return (\n        types.isIdentifier(node.key) &&\n        types.isFunctionExpression(node.value) &&\n        ((checkInitOnly && node.key.name === 'init') ||\n          (!checkInitOnly &&\n            (node.key.name === 'init' ||\n              (!isGlimmerComponent && ember.isComponentLifecycleHook(node)) ||\n              (isGlimmerComponent && ember.isGlimmerComponentLifecycleHook(node)))))\n      );\n    }\n\n    function checkAndReport(\n      node,\n      isInEmberComponent,\n      isInEmberController,\n      isInEmberRoute,\n      isInEmberMixin,\n      isInEmberService,\n      isInGlimmerComponent,\n      isNativeClass\n    ) {\n      const hookName = node.key.name;\n\n      if (\n        hookName === 'init' &&\n        !isInEmberComponent &&\n        !isInEmberController &&\n        !isInEmberRoute &&\n        !isInEmberMixin &&\n        !isInEmberService\n      ) {\n        // Checking `init` hook but not inside any Ember class.\n        return;\n      } else if (\n        hookName !== 'init' &&\n        !isInEmberComponent &&\n        !isInEmberMixin &&\n        !isInGlimmerComponent\n      ) {\n        // Checking a component lifecycle hook but not inside a component/mixin which could have them.\n        return;\n      }\n\n      const body = isNativeSuper ? node.value.body : node.body;\n      const hasSuper = hasMatchingNode(body, (bodyChild) =>\n        isNativeClass ? isNativeSuper(bodyChild, hookName) : isClassicSuper(bodyChild)\n      );\n      if (!hasSuper) {\n        report(node, isNativeClass, hookName);\n      }\n    }\n\n    let currentEmberComponent = null;\n    let currentEmberController = null;\n    let currentEmberRoute = null;\n    let currentEmberMixin = null;\n    let currentEmberService = null;\n    let currentGlimmerComponent = null;\n\n    return {\n      ClassDeclaration(node) {\n        if (ember.isEmberComponent(context, node)) {\n          currentEmberComponent = node;\n        } else if (ember.isEmberController(context, node)) {\n          currentEmberController = node;\n        } else if (ember.isEmberRoute(context, node)) {\n          currentEmberRoute = node;\n        } else if (ember.isEmberMixin(context, node)) {\n          currentEmberMixin = node;\n        } else if (ember.isEmberService(context, node)) {\n          currentEmberService = node;\n        } else if (ember.isGlimmerComponent(context, node)) {\n          currentGlimmerComponent = node;\n        }\n      },\n\n      'ClassDeclaration:exit'(node) {\n        switch (node) {\n          case currentEmberComponent: {\n            currentEmberComponent = null;\n\n            break;\n          }\n          case currentEmberController: {\n            currentEmberController = null;\n\n            break;\n          }\n          case currentEmberRoute: {\n            currentEmberRoute = null;\n\n            break;\n          }\n          case currentEmberMixin: {\n            currentEmberMixin = null;\n\n            break;\n          }\n          case currentEmberService: {\n            currentEmberService = null;\n\n            break;\n          }\n          case currentGlimmerComponent: {\n            currentGlimmerComponent = null;\n\n            break;\n          }\n          // No default\n        }\n      },\n\n      MethodDefinition(node) {\n        if (!checkNativeClasses) {\n          // Option off.\n          return;\n        }\n\n        if (!isLifecycleHook(node, currentGlimmerComponent)) {\n          return;\n        }\n\n        checkAndReport(\n          node,\n          currentEmberComponent,\n          currentEmberController,\n          currentEmberRoute,\n          currentEmberMixin,\n          currentEmberService,\n          currentGlimmerComponent,\n          true\n        );\n      },\n\n      Property(node) {\n        const parentParent = node.parent.parent;\n        if (!types.isCallExpression(parentParent)) {\n          // Not inside potential Ember class.\n          return;\n        }\n\n        if (!isLifecycleHook(node)) {\n          return;\n        }\n\n        checkAndReport(\n          node,\n          ember.isEmberComponent(context, parentParent),\n          ember.isEmberController(context, parentParent),\n          ember.isEmberRoute(context, parentParent),\n          ember.isEmberMixin(context, parentParent),\n          ember.isEmberService(context, parentParent),\n          false,\n          false\n        );\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-tagless-components.js",
    "content": "'use strict';\n\nconst {\n  isCallExpression,\n  isIdentifier,\n  isObjectExpression,\n  isStringLiteral,\n} = require('../utils/types');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\nconst { getImportIdentifier } = require('../utils/import');\nconst { isTestFile } = require('../utils/ember');\n\nconst ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS =\n  \"Please switch to a tagless component by setting `tagName: ''` or converting to a Glimmer component\";\n\n/**\n * Whether or not the node represents an identifier called `tagName`\n *\n * @param {object} node\n * @param {boolean}\n */\nfunction isTagNameIdentifier(node) {\n  return isIdentifier(node) && node.name === 'tagName';\n}\n\n/**\n * Whether or not the node represents a property called `tagName`\n *\n * @param {object} node\n * @return {boolean}\n */\nfunction isTagNameProperty(node) {\n  return isTagNameIdentifier(node.key);\n}\n\n/**\n * @param {Property|ClassProperty|PropertyDefinition} node\n * @return {boolean}\n */\nfunction isNonEmptyTagNameProperty(node) {\n  return isTagNameProperty(node) && isStringLiteral(node.value) && node.value.value !== '';\n}\n\n/**\n * @param {ObjectExpression} node\n * @return {Property=}\n */\nfunction getNonEmptyTagNameInObjectExpression(node) {\n  return node.properties.find(isNonEmptyTagNameProperty);\n}\n\n/**\n * @param {ClassBody} node\n * @return {ClassProperty|PropertyDefinition=}\n */\nfunction getNonEmptyTagNameInClassBody(node) {\n  return node.body.find(isNonEmptyTagNameProperty);\n}\n\n/**\n * @param {ObjectExpression} node\n * @return {boolean}\n */\nfunction hasNoTagNameInObjectExpression(node) {\n  return !node.properties.some(isTagNameProperty);\n}\n\n/**\n * @param {ClassBody} node\n * @return {boolean}\n */\nfunction hasNoTagNameInClassBody(node) {\n  return !node.body.some(isTagNameProperty);\n}\n\n/**\n * @param {ClassBody} node\n * @param {string} name\n * @return {Decorator | undefined}\n */\nfunction getDecoratorCallExpressionWithName(node, name) {\n  return (\n    node.decorators &&\n    node.decorators.find((d) => isCallExpression(d.expression) && d.expression.callee.name === name)\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS,\n\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow using the wrapper element of a component',\n      category: 'Components',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-tagless-components.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    if (isTestFile(context.filename)) {\n      // This rule does not apply to test files.\n      return {};\n    }\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    let importedComponentName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/component') {\n          importedComponentName =\n            importedComponentName || getImportIdentifier(node, '@ember/component');\n        }\n      },\n\n      // Handle classic components\n      'CallExpression > MemberExpression[property.name=\"extend\"]'(node) {\n        const callExpression = node.parent;\n\n        if (!(node.object.type === 'Identifier' && node.object.name === importedComponentName)) {\n          // Not an Ember component.\n          return;\n        }\n\n        // Handle `.extend` being called with no arguments\n        if (callExpression.arguments.length === 0) {\n          context.report({\n            node: callExpression,\n            message: ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS,\n          });\n        }\n\n        for (const arg of callExpression.arguments) {\n          const resultingNode = getNodeOrNodeFromVariable(arg, scopeManager);\n\n          // Ignore anything other than an object literal, since Mixins can be in here too\n          if (!isObjectExpression(resultingNode)) {\n            continue;\n          }\n\n          let tagNameNode;\n\n          if ((tagNameNode = getNonEmptyTagNameInObjectExpression(resultingNode))) {\n            context.report({\n              node: tagNameNode,\n              message: ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS,\n            });\n          } else if (hasNoTagNameInObjectExpression(resultingNode)) {\n            context.report({\n              node: callExpression,\n              message: ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS,\n            });\n          }\n        }\n      },\n\n      // Handle ES class components\n      'ClassDeclaration[superClass]'(node) {\n        if (\n          node.superClass.type === 'Identifier' &&\n          node.superClass.name === importedComponentName\n        ) {\n          let tagNameNode;\n          let decorator;\n\n          if ((tagNameNode = getNonEmptyTagNameInClassBody(node.body))) {\n            context.report({\n              node: tagNameNode,\n              message: ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS,\n            });\n          } else if ((decorator = getDecoratorCallExpressionWithName(node, 'tagName'))) {\n            const tagNameArg = decorator.expression.arguments[0];\n\n            if (isStringLiteral(tagNameArg) && tagNameArg.value !== '') {\n              context.report({\n                node: decorator,\n                message: ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS,\n              });\n            }\n          } else if (hasNoTagNameInClassBody(node.body)) {\n            context.report({ node: node.body, message: ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/require-valid-css-selector-in-test-helpers.js",
    "content": "'use strict';\n\nconst { getPropertyValue, parseCallee } = require('../utils/utils');\nconst cssTree = require('css-tree');\nconst emberUtils = require('../utils/ember');\n\n/**\n * Positive lookahead regexp.\n * Used for splitting a string only by commas that are not inside quotes.\n * Example input string:\n *  - [data-test-row=\"London, England, GB\"], .foo\n * Would match these strings:\n *  - [data-test-row=\"London, England, GB\"]\n *  - .foo\n * https://stackoverflow.com/questions/23582276/split-string-by-comma-but-ignore-commas-inside-quotes/23582323\n */\nconst REGEX_SPLIT_BY_COMMA_NOT_IN_QUOTES = /,(?=(?:(?:[^\"']*[\"']){2})*[^\"']*$)/g;\n\nconst TEST_MODULE_NAMES = new Set(['module', 'describe']);\nconst TEST_HELPER_IMPORTS = new Set([\n  'blur',\n  'click',\n  'doubleClick',\n  'fillIn',\n  'find',\n  'findAll',\n  'focus',\n  'scrollTo',\n  'select',\n  'tap',\n  'triggerEvent',\n  'triggerKeyEvent',\n  'typeIn',\n  'waitFor',\n]);\nconst QUERY_SELECTOR_METHODS = new Set(['querySelectorAll', 'querySelector']);\nconst PARENT_NODE_NAMES = new Set(['element', 'document']);\nconst SELECTOR_RULES = Object.freeze({\n  unclosedAttr: {\n    hasError: (str) =>\n      str\n        .split(REGEX_SPLIT_BY_COMMA_NOT_IN_QUOTES)\n        .map((str) => str.trim())\n        .some((selector) => hasMissingClosingBracket(selector)),\n    fix(node, str, fixer) {\n      const replacement = str\n        .split(REGEX_SPLIT_BY_COMMA_NOT_IN_QUOTES)\n        .map((selector) => (hasMissingClosingBracket(selector) ? `${selector}]` : selector))\n        .join(',');\n      return fixer.replaceText(node.arguments[0], `'${replacement}'`);\n    },\n    errorMessage:\n      'Syntax error, you used an unclosed attribute selector: \"{{selector}}\", should be: \"{{selector}}]\"',\n  },\n  idStartsWithNumber: {\n    hasError: (str) =>\n      str\n        .split(REGEX_SPLIT_BY_COMMA_NOT_IN_QUOTES)\n        .map((str) => str.trim())\n        .some((selector) => selector.match(/^#\\d/)),\n    fix() {},\n    errorMessage: 'Syntax error, ids cannot start with a number: \"{{selector}}\"',\n  },\n  other: {\n    hasError: (str) =>\n      str\n        .split(REGEX_SPLIT_BY_COMMA_NOT_IN_QUOTES)\n        .map((str) => str.trim())\n        .some((selector) => !_isValidSelector(selector)),\n    fix() {},\n    errorMessage: 'Syntax error, \"{{selector}}\" is not a valid selector',\n  },\n});\n\nfunction hasMissingClosingBracket(selector) {\n  return selector.includes('[') && !selector.includes(']');\n}\n\nfunction _isValidSelector(selector) {\n  try {\n    cssTree.parse(selector, {\n      context: 'selector',\n    });\n  } catch {\n    return false;\n  }\n\n  return true;\n}\n\nfunction _isAssertDomCall(node, assertIdentifierName) {\n  const calleeName = parseCallee(node) || [];\n\n  if (calleeName[1] !== 'dom' || !assertIdentifierName) {\n    return false;\n  }\n\n  return calleeName[0] === assertIdentifierName;\n}\n\nfunction _isQuerySelectorCall(node, testModuleSpecifier) {\n  const calleeName = parseCallee(node);\n\n  // only validate querySelector calls in the test module context\n  if (!testModuleSpecifier) {\n    return false;\n  }\n\n  return QUERY_SELECTOR_METHODS.has(calleeName[1]) && PARENT_NODE_NAMES.has(calleeName[0]);\n}\n\nfunction _isTestHelperCall(node, hasTestHelperImport, localImportNames) {\n  const calleeName = parseCallee(node) || [];\n\n  return hasTestHelperImport && localImportNames.includes(calleeName[0]);\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow using invalid CSS selectors in test helpers',\n      category: 'Testing',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-valid-css-selector-in-test-helpers.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: Object.keys(SELECTOR_RULES).reduce((accumulator, currentVal) => {\n      const _accumulator = accumulator || {};\n      _accumulator[currentVal] = SELECTOR_RULES[currentVal].errorMessage;\n\n      return _accumulator;\n    }, null),\n  },\n  create(context) {\n    if (!emberUtils.isTestFile(context.filename)) {\n      // This rule does not apply to test files.\n      return {};\n    }\n    let hasTestHelperImport = false;\n    let localImportNames = [];\n    let testImportLocalName = '';\n    let testModuleSpecifier = '';\n    let assertIdentifierName = '';\n\n    return {\n      'ImportDeclaration[source.value=\"@ember/test-helpers\"]'(node) {\n        hasTestHelperImport = getPropertyValue(node, 'specifiers').find((specifier) =>\n          TEST_HELPER_IMPORTS.has(getPropertyValue(specifier, 'imported.name'))\n        );\n\n        localImportNames = getPropertyValue(node, 'specifiers')\n          .filter((specifier) =>\n            TEST_HELPER_IMPORTS.has(getPropertyValue(specifier, 'imported.name'))\n          )\n          .map((specifier) => getPropertyValue(specifier, 'local.name'));\n      },\n      ImportDeclaration(node) {\n        if (!node.source.value === 'qunit' && !node.source.value === 'mocha') {\n          return;\n        }\n\n        const testImportSpecifier = getPropertyValue(node, 'specifiers').find(\n          (specifier) => getPropertyValue(specifier, 'imported.name') === 'test'\n        );\n\n        if (!testModuleSpecifier) {\n          testModuleSpecifier = getPropertyValue(node, 'specifiers').find((specifier) =>\n            TEST_MODULE_NAMES.has(getPropertyValue(specifier, 'imported.name'))\n          );\n        }\n\n        if (testImportSpecifier) {\n          testImportLocalName = getPropertyValue(testImportSpecifier, 'local.name');\n        }\n      },\n      CallExpression(node) {\n        if (node.callee.name === testImportLocalName) {\n          const [, testFn] = node.arguments || [];\n\n          if (testFn) {\n            assertIdentifierName = getPropertyValue(testFn, 'params.0.name');\n          }\n        }\n        const value = getPropertyValue(node, 'arguments.0.value');\n\n        if (\n          typeof value !== 'string' ||\n          (!_isAssertDomCall(node, assertIdentifierName) &&\n            !_isTestHelperCall(node, hasTestHelperImport, localImportNames) &&\n            !_isQuerySelectorCall(node, testModuleSpecifier))\n        ) {\n          return;\n        }\n\n        const failureRule = Object.keys(SELECTOR_RULES).find((invalidSelectorKey) =>\n          SELECTOR_RULES[invalidSelectorKey].hasError(value)\n        );\n\n        if (failureRule) {\n          context.report({\n            node,\n            messageId: failureRule,\n            data: { selector: value },\n            fix: SELECTOR_RULES[failureRule].fix.bind(null, node, value),\n          });\n        }\n      },\n      'CallExpression:exit'(node) {\n        if (node.callee.name === testImportLocalName) {\n          assertIdentifierName = '';\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/route-path-style.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst ember = require('../utils/ember');\nconst kebabCase = require('lodash.kebabcase');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\n\n//------------------------------------------------------------------------------\n// Rule Definition\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = 'Use kebab-case in route path.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description:\n        'enforce usage of kebab-case (instead of snake_case or camelCase) in route paths',\n      category: 'Routes',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/route-path-style.md',\n    },\n    fixable: null,\n    hasSuggestions: true,\n    schema: [],\n    messages: {\n      convertToKebabCase: 'Convert route path to kebab case',\n    },\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (!ember.isRoute(node) || node.arguments.length === 0) {\n          return;\n        }\n\n        // Retrieve the path based on the call format used:\n        // 1. this.route('route-and-path');\n        // 2. this.route('route', { path: 'path' });\n\n        let optionsNode;\n        const hasExplicitPathOption =\n          node.arguments.length >= 2 &&\n          (optionsNode = getNodeOrNodeFromVariable(node.arguments[1], scopeManager)) &&\n          optionsNode.type === 'ObjectExpression' &&\n          hasPropertyWithKeyName(optionsNode, 'path');\n        const pathValueNode = hasExplicitPathOption\n          ? getPropertyByKeyName(optionsNode, 'path').value\n          : node.arguments[0];\n\n        if (!types.isStringLiteral(pathValueNode)) {\n          return;\n        }\n\n        const urlSegments = getStaticURLSegments(pathValueNode.value);\n\n        if (urlSegments.some((urlPart) => !isKebabCase(urlPart))) {\n          context.report({\n            node: pathValueNode,\n            message: ERROR_MESSAGE,\n            suggest: [\n              {\n                messageId: 'convertToKebabCase',\n                fix(fixer) {\n                  return fixer.replaceTextRange(\n                    [pathValueNode.range[0] + 1, pathValueNode.range[1] - 1],\n                    convertRoutePathToKebabCase(pathValueNode.value)\n                  );\n                },\n              },\n            ],\n          });\n        }\n      },\n    };\n  },\n};\n\nfunction hasPropertyWithKeyName(objectExpression, keyName) {\n  return getPropertyByKeyName(objectExpression, keyName) !== undefined;\n}\n\nfunction getPropertyByKeyName(objectExpression, keyName) {\n  return objectExpression.properties.find(\n    (property) =>\n      types.isProperty(property) &&\n      types.isIdentifier(property.key) &&\n      property.key.name === keyName\n  );\n}\n\nfunction getStaticURLSegments(path) {\n  return path.split('/').filter((segment) => isStaticSegment(segment));\n}\n\nfunction isDynamicSegment(segment) {\n  return segment.includes(':');\n}\n\nfunction isWildcardSegment(segment) {\n  return segment.includes('*');\n}\n\nfunction isStaticSegment(segment) {\n  return !isDynamicSegment(segment) && !isWildcardSegment(segment) && segment !== '';\n}\n\nconst KEBAB_CASE_REGEXP = /^[\\da-z-]+$/;\nfunction isKebabCase(str) {\n  return str.match(KEBAB_CASE_REGEXP);\n}\n\nfunction convertRoutePathToKebabCase(path) {\n  return path\n    .split('/')\n    .map((segment) => (isStaticSegment(segment) ? kebabCase(segment) : segment))\n    .join('/');\n}\n"
  },
  {
    "path": "lib/rules/routes-segments-snake-case.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst ember = require('../utils/ember');\nconst { snakeCase } = require('snake-case');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\n\n//------------------------------------------------------------------------------\n// Routing - Snake case in dynamic segments of routes\n//------------------------------------------------------------------------------\n\nconst isNotSnakeCase = function (name) {\n  return snakeCase(name) !== name;\n};\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of snake_cased dynamic segments in routes',\n      category: 'Routes',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/routes-segments-snake-case.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  create(context) {\n    const message = 'Use snake case in dynamic segments of routes';\n    const routeSegmentRegex = /:([\\w-]+)/g;\n\n    const report = function (node) {\n      context.report({ node, message });\n    };\n\n    const isSegment = function (property) {\n      return (\n        types.isProperty(property) &&\n        types.isIdentifier(property.key) &&\n        property.key.name === 'path' &&\n        routeSegmentRegex.test(property.value.value)\n      );\n    };\n\n    const getSegmentNames = function (property) {\n      if (!isSegment(property)) {\n        return [];\n      }\n\n      return property.value.value.match(routeSegmentRegex).map((segment) => segment.slice(1));\n    };\n\n    const sourceCode = context.sourceCode;\n    const { scopeManager } = sourceCode;\n\n    return {\n      CallExpression(node) {\n        if (!ember.isRoute(node)) {\n          return;\n        }\n\n        let optionsNode;\n        const routeOptions =\n          node.arguments.length >= 2 &&\n          (optionsNode = getNodeOrNodeFromVariable(node.arguments[1], scopeManager)) &&\n          optionsNode.type === 'ObjectExpression'\n            ? optionsNode\n            : false;\n\n        if (routeOptions) {\n          for (const property of routeOptions.properties) {\n            const segmentNames = getSegmentNames(property);\n\n            if (segmentNames.some(isNotSnakeCase)) {\n              report(property.value);\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-attribute-indentation.js",
    "content": "'use strict';\n\nfunction getWhiteSpaceLength(statement) {\n  const whiteSpace = statement.match(/^\\s+/) || [];\n  return (whiteSpace[0] || '').length;\n}\n\nfunction getEndLocationForOpen(node) {\n  return node.type === 'GlimmerBlockStatement' ? node.program.loc.start : node.loc.end;\n}\n\nfunction canApplyRule(node, config, sourceCode) {\n  let end;\n  if (node.type === 'GlimmerElementNode') {\n    // Use the first `>` token to find the end of the opening tag\n    const tokens = sourceCode.getTokens(node);\n    const openEnd = tokens.find((t) => t.value === '>');\n    end = openEnd ? openEnd.loc.end : node.loc.end;\n  } else {\n    end = getEndLocationForOpen(node);\n  }\n  const start = node.loc.start;\n  if (start.line === end.line) {\n    return end.column - start.column > config.maxLength;\n  }\n  return true;\n}\n\nfunction getSourceForLoc(sourceLines, loc) {\n  const startLine = loc.start.line;\n  const startColumn = loc.start.column;\n  const endLine = loc.end?.line || startLine;\n  const endColumn = loc.end?.column;\n\n  if (startLine === endLine) {\n    return endColumn === undefined\n      ? sourceLines[startLine - 1].slice(startColumn)\n      : sourceLines[startLine - 1].slice(startColumn, endColumn);\n  }\n\n  const lines = [];\n  for (let i = startLine; i <= endLine; i++) {\n    if (i === startLine) {\n      lines.push(sourceLines[i - 1].slice(startColumn));\n    } else if (i === endLine && endColumn !== undefined) {\n      lines.push(sourceLines[i - 1].slice(0, endColumn));\n    } else {\n      lines.push(sourceLines[i - 1]);\n    }\n  }\n  return lines.join('\\n');\n}\n\nfunction getSourceForNode(sourceLines, node) {\n  return getSourceForLoc(sourceLines, node.loc);\n}\n\nfunction parseOptions(options) {\n  if (!options || typeof options !== 'object') {\n    return {\n      maxLength: 80,\n      indentation: 2,\n      processElements: true,\n      mustacheOpenEnd: 'new-line',\n      elementOpenEnd: 'new-line',\n    };\n  }\n\n  const result = {\n    maxLength: 80,\n    indentation: 2,\n    mustacheOpenEnd: 'new-line',\n    elementOpenEnd: 'new-line',\n  };\n\n  if ('open-invocation-max-len' in options) {\n    result.maxLength = options['open-invocation-max-len'];\n  }\n  if ('indentation' in options) {\n    result.indentation = options.indentation;\n  }\n  if ('process-elements' in options) {\n    result.processElements = options['process-elements'];\n  }\n  if ('mustache-open-end' in options) {\n    result.mustacheOpenEnd = options['mustache-open-end'];\n  }\n  if ('element-open-end' in options) {\n    result.processElements = true;\n    result.elementOpenEnd = options['element-open-end'];\n  }\n  if ('as-indentation' in options) {\n    result.asIndentation = options['as-indentation'];\n  }\n  return result;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce proper indentation of attributes and arguments in multi-line templates',\n      category: 'Stylistic Issues',\n      recommended: false,\n      recommendedGjs: false,\n      recommendedGts: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-attribute-indentation.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          'open-invocation-max-len': { type: 'integer', minimum: 0 },\n          indentation: { type: 'integer', minimum: 0 },\n          'process-elements': { type: 'boolean' },\n          'mustache-open-end': { enum: ['new-line', 'last-attribute'] },\n          'element-open-end': { enum: ['new-line', 'last-attribute'] },\n          'as-indentation': { enum: ['attribute', 'closing-brace'] },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      incorrectParamIndentation:\n        \"Incorrect indentation of {{paramType}} '{{paramName}}' beginning at L{{actualLine}}:C{{actualColumn}}. Expected '{{paramName}}' to be at L{{expectedLine}}:C{{expectedColumn}}.\",\n      incorrectCloseBrace:\n        \"Incorrect indentation of close curly braces '}}' for the component '{{{{componentName}}}}' beginning at L{{actualLine}}:C{{actualColumn}}. Expected '{{{{componentName}}}}' to be at L{{expectedLine}}:C{{expectedColumn}}.\",\n      incorrectCloseBracket:\n        \"Incorrect indentation of close bracket '>' for the element '<{{tagName}}>' beginning at L{{actualLine}}:C{{actualColumn}}. Expected '<{{tagName}}>' to be at L{{expectedLine}}:C{{expectedColumn}}.\",\n      incorrectBlockParamIndentation:\n        \"Incorrect indentation of block params '{{blockParamStatement}}' beginning at L{{actualLine}}:C{{actualColumn}}. Expecting the block params to be at L{{expectedLine}}:C{{expectedColumn}}.\",\n      incorrectClosingTag:\n        \"Incorrect indentation of close tag '</{{tagName}}>' for element '<{{tagName}}>' beginning at L{{actualLine}}:C{{actualColumn}}. Expected '</{{tagName}}>' to be at L{{expectedLine}}:C{{expectedColumn}}.\",\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/attribute-indentation.js',\n      docs: 'docs/rule/attribute-indentation.md',\n      tests: 'test/unit/rules/attribute-indentation-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseOptions(context.options[0]);\n    const sourceCode = context.sourceCode;\n    const sourceLines = sourceCode.getText().split('\\n');\n\n    function getLineIndentation(node) {\n      const currentLine = sourceLines[node.loc.start.line - 1];\n      const leadingWhitespace = getWhiteSpaceLength(currentLine);\n      if (leadingWhitespace === 0) {\n        return node.loc.start.column;\n      }\n      return leadingWhitespace;\n    }\n\n    function getBlockParamStartLoc(node) {\n      let actual, expected;\n      const actualProgramStartLine = /^\\s*}}/.test(sourceLines[node.program.loc.start.line - 1])\n        ? 1\n        : 0;\n      const programStartLoc = {\n        line: node.program.loc.start.line - actualProgramStartLine,\n        column: node.program.loc.start.column,\n      };\n      const nodeStart = node.loc.start;\n      if (node.params.length === 0 && (!node.hash || node.hash.pairs.length === 0)) {\n        expected = {\n          line: nodeStart.line + 1,\n          column: nodeStart.column,\n        };\n        if (nodeStart.line === programStartLoc.line) {\n          const displayName = `{{#${node.path.original}`;\n          actual = {\n            line: nodeStart.line,\n            column: displayName.length,\n          };\n        } else {\n          const source = getSourceForLoc(sourceLines, {\n            start: {\n              line: programStartLoc.line,\n              column: 0,\n            },\n            end: programStartLoc,\n          });\n          actual = {\n            line: programStartLoc.line,\n            column: getWhiteSpaceLength(source),\n          };\n        }\n      } else {\n        let paramOrHashPairEndLoc;\n\n        if (node.params.length > 0) {\n          paramOrHashPairEndLoc = node.params.at(-1).loc.end;\n        }\n\n        if (node.hash && node.hash.pairs.length > 0) {\n          paramOrHashPairEndLoc = node.hash.loc.end;\n        }\n\n        const indentation = config.asIndentation === 'attribute' ? 2 : 0;\n        expected = {\n          line: paramOrHashPairEndLoc.line + 1,\n          column: node.loc.start.column + indentation,\n        };\n        if (paramOrHashPairEndLoc.line === programStartLoc.line) {\n          actual = paramOrHashPairEndLoc;\n        } else if (paramOrHashPairEndLoc.line < programStartLoc.line) {\n          const loc = {\n            start: paramOrHashPairEndLoc,\n            end: {\n              line: paramOrHashPairEndLoc.line,\n            },\n          };\n\n          const hashPairLineEndSource = getSourceForLoc(sourceLines, loc).trim();\n\n          actual = hashPairLineEndSource\n            ? paramOrHashPairEndLoc\n            : {\n                line: programStartLoc.line,\n                column: getWhiteSpaceLength(sourceLines[programStartLoc.line - 1]),\n              };\n        }\n      }\n      return { actual, expected };\n    }\n\n    function validateBlockParams(node) {\n      const location = getBlockParamStartLoc(node);\n      const actual = location.actual;\n      const expected = location.expected;\n\n      if (actual.line !== expected.line || actual.column !== expected.column) {\n        const blockParamStatement = getSourceForLoc(sourceLines, {\n          start: actual,\n          end: node.program.loc.start,\n        }).trim();\n\n        context.report({\n          node,\n          messageId: 'incorrectBlockParamIndentation',\n          loc: { line: actual.line, column: actual.column },\n          data: {\n            blockParamStatement,\n            actualLine: actual.line,\n            actualColumn: actual.column,\n            expectedLine: expected.line,\n            expectedColumn: expected.column,\n          },\n        });\n      }\n      const expectedColumnNextLocation =\n        node.type === 'GlimmerElementNode' && !node.selfClosing ? 1 : 2;\n      return {\n        line: expected.line + 1,\n        column: expected.column + node.program.loc.start.column - expectedColumnNextLocation,\n      };\n    }\n\n    function iterateParams(params, type, initialExpectedLineStart, expectedColumnStart, node) {\n      let expectedLineStart = initialExpectedLineStart;\n      let paramType = type;\n      let namePath;\n\n      switch (type) {\n        case 'positional': {\n          paramType = 'positional param';\n          namePath = 'original';\n          break;\n        }\n        case 'htmlAttribute': {\n          paramType = 'htmlAttribute';\n          namePath = 'name';\n          break;\n        }\n        case 'element modifier': {\n          paramType = 'element modifier';\n          break;\n        }\n        default: {\n          paramType = type;\n          namePath = 'key';\n        }\n      }\n\n      let nextColumn = expectedColumnStart;\n      for (const param of params) {\n        const actualStartLocation = param.loc.start;\n        nextColumn = param.loc.end.column;\n        if (\n          expectedLineStart !== actualStartLocation.line ||\n          expectedColumnStart !== actualStartLocation.column\n        ) {\n          const paramName = param[namePath] || param.path?.original;\n          context.report({\n            node: param,\n            messageId: 'incorrectParamIndentation',\n            loc: { line: actualStartLocation.line, column: actualStartLocation.column },\n            data: {\n              paramType,\n              paramName,\n              actualLine: actualStartLocation.line,\n              actualColumn: actualStartLocation.column,\n              expectedLine: expectedLineStart,\n              expectedColumn: expectedColumnStart,\n            },\n          });\n        }\n\n        const paramValueType = param.value ? param.value.type : param.type;\n        if (paramValueType === 'GlimmerSubExpression' || paramValueType === 'SubExpression') {\n          if (param.loc.start.line !== param.loc.end.line) {\n            expectedLineStart = param.loc.end.line;\n          }\n        } else if (\n          paramValueType === 'GlimmerMustacheStatement' ||\n          paramValueType === 'MustacheStatement'\n        ) {\n          expectedLineStart = param.value.loc.end.line;\n          nextColumn = param.value.loc.end.column;\n        }\n\n        expectedLineStart++;\n      }\n\n      return {\n        line: expectedLineStart,\n        column: nextColumn,\n      };\n    }\n\n    function validateParams(node) {\n      const leadingWhitespace = getLineIndentation(node);\n      const expectedColumnStart = leadingWhitespace + config.indentation;\n      const expectedLineStart = node.loc.start.line + 1;\n\n      let nextLocation = {\n        line: expectedLineStart,\n        column: node.loc.start.column,\n      };\n\n      if (node.type === 'GlimmerElementNode') {\n        if (node.attributes.length > 0) {\n          nextLocation = iterateParams(\n            node.attributes,\n            'htmlAttribute',\n            expectedLineStart,\n            expectedColumnStart,\n            node\n          );\n        }\n\n        if (node.modifiers.length > 0) {\n          nextLocation = iterateParams(\n            node.modifiers,\n            'element modifier',\n            nextLocation.line,\n            expectedColumnStart,\n            node\n          );\n        }\n      } else {\n        if (node.params.length > 0) {\n          nextLocation = iterateParams(\n            node.params,\n            'positional',\n            expectedLineStart,\n            expectedColumnStart,\n            node\n          );\n        }\n        if (node.hash && node.hash.pairs.length > 0) {\n          nextLocation = iterateParams(\n            node.hash.pairs,\n            'attribute',\n            nextLocation.line,\n            expectedColumnStart,\n            node\n          );\n        }\n      }\n\n      return nextLocation;\n    }\n\n    function validateCloseBrace(node, nextLocation) {\n      const openIndentation = getLineIndentation(node);\n\n      let actualStartLocation;\n\n      if (node.type === 'GlimmerElementNode') {\n        // Use tokens to find the actual `>` position\n        const tokens = sourceCode.getTokens(node);\n        const openEnd = tokens.find((t) => t.value === '>');\n        if (!openEnd) {\n          return;\n        }\n        // For self-closing `/>`, the `>` is preceded by `/`\n        if (node.selfClosing) {\n          const slashToken = tokens.find((t) => t.value === '/' && t.range[1] === openEnd.range[0]);\n          actualStartLocation = slashToken ? slashToken.loc.start : openEnd.loc.start;\n        } else {\n          actualStartLocation = openEnd.loc.start;\n        }\n      } else {\n        const end = getEndLocationForOpen(node);\n        const actualColumnStartLocation =\n          node.type === 'GlimmerMustacheStatement' && node.trusting ? 3 : 2;\n\n        actualStartLocation = {\n          line: end.line,\n          column: end.column - actualColumnStartLocation,\n        };\n      }\n\n      const endPosition =\n        node.type === 'GlimmerElementNode' ? config.elementOpenEnd : config.mustacheOpenEnd;\n      const expectedStartLocation = {\n        line: endPosition === 'last-attribute' ? nextLocation.line - 1 : nextLocation.line,\n        column: endPosition === 'last-attribute' ? nextLocation.column : openIndentation,\n      };\n\n      if (\n        actualStartLocation.line !== expectedStartLocation.line ||\n        actualStartLocation.column !== expectedStartLocation.column\n      ) {\n        if (node.type === 'GlimmerElementNode') {\n          const tagName = node.tag;\n          context.report({\n            node,\n            messageId: 'incorrectCloseBracket',\n            loc: { line: actualStartLocation.line, column: actualStartLocation.column },\n            data: {\n              tagName,\n              actualLine: actualStartLocation.line,\n              actualColumn: actualStartLocation.column,\n              expectedLine: expectedStartLocation.line,\n              expectedColumn: expectedStartLocation.column,\n            },\n          });\n        } else {\n          const componentName = node.path.original;\n          context.report({\n            node,\n            messageId: 'incorrectCloseBrace',\n            loc: { line: actualStartLocation.line, column: actualStartLocation.column },\n            data: {\n              componentName,\n              actualLine: actualStartLocation.line,\n              actualColumn: actualStartLocation.column,\n              expectedLine: expectedStartLocation.line,\n              expectedColumn: expectedStartLocation.column,\n            },\n          });\n        }\n      }\n    }\n\n    function validateClosingTag(node, lastChildEndLine) {\n      // `</tag>` is `2 + tag.length + 1` chars: `</` + tag + `>`\n      const actualColumnStartLocation = 3 + node.tag.length;\n      const actualColumn = node.loc.end.column - actualColumnStartLocation;\n      const expectedColumn = node.loc.start.column;\n      const actualLine = node.loc.end.line;\n\n      // Closing tag must not appear before the last child ends (line check),\n      // and must be column-aligned with the opening tag.\n      // Note: Glimmer AST may not include trailing whitespace TextNodes, so the\n      // closing tag can be on a LATER line than lastChildEndLine (whitespace gap).\n      // The original ember-template-lint's strict line equality works because\n      // Handlebars AST includes trailing TextNodes that span to the closing tag line.\n      if (actualLine < lastChildEndLine || actualColumn !== expectedColumn) {\n        context.report({\n          node,\n          messageId: 'incorrectClosingTag',\n          loc: { line: actualLine, column: actualColumn },\n          data: {\n            tagName: node.tag,\n            actualLine,\n            actualColumn,\n            expectedLine: lastChildEndLine,\n            expectedColumn,\n          },\n        });\n      }\n    }\n\n    function validateNonBlockForm(node) {\n      if (node.params.length > 0 || (node.hash && node.hash.pairs.length > 0)) {\n        const nextLocation = validateParams(node);\n        validateCloseBrace(node, nextLocation);\n        return nextLocation;\n      }\n      return undefined;\n    }\n\n    function validateBlockForm(node) {\n      let nextLocation;\n      if (node.params.length > 0 || (node.hash && node.hash.pairs.length > 0)) {\n        nextLocation = validateParams(node);\n      }\n      if (node.program?.blockParams && node.program.blockParams.length > 0) {\n        nextLocation = validateBlockParams(node);\n      }\n      if (nextLocation) {\n        validateCloseBrace(node, nextLocation);\n      }\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        if (canApplyRule(node, config, sourceCode)) {\n          validateBlockForm(node);\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (canApplyRule(node, config, sourceCode)) {\n          validateNonBlockForm(node);\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (config.processElements) {\n          if (canApplyRule(node, config, sourceCode)) {\n            if (node.modifiers.length > 0 || node.attributes.length > 0) {\n              const expectedCloseBraceLocation = validateParams(node);\n              validateCloseBrace(node, expectedCloseBraceLocation);\n            }\n\n            if (node.children.length > 0) {\n              const lastChild = node.children.at(-1);\n              const expectedStartLine =\n                lastChild.type === 'GlimmerBlockStatement'\n                  ? lastChild.loc.end.line + 1\n                  : lastChild.loc.end.line;\n              validateClosingTag(node, expectedStartLine);\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-attribute-order.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce consistent ordering of attributes in template elements',\n      category: 'Stylistic Issues',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-attribute-order.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          order: {\n            type: 'array',\n            items: {\n              type: 'string',\n            },\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      wrongOrder: 'Attribute \"{{currentAttr}}\" should come {{position}} \"{{expectedAttr}}\".',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/attribute-order.js',\n      docs: 'docs/rule/attribute-order.md',\n      tests: 'test/unit/rules/attribute-order-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    const options = context.options[0] || {};\n    const order = options.order || [\n      'class',\n      'id',\n      'role',\n      'aria-',\n      'data-test-',\n      'type',\n      'name',\n      'value',\n      'placeholder',\n      'disabled',\n    ];\n\n    function getAttributeCategory(attrName) {\n      for (const category of order) {\n        if (category.endsWith('-')) {\n          if (attrName.startsWith(category)) {\n            return category;\n          }\n        } else if (attrName === category) {\n          return category;\n        }\n      }\n      return null;\n    }\n\n    function getExpectedIndex(attrName) {\n      const category = getAttributeCategory(attrName);\n      if (category === null) {\n        return order.length; // Unknown attributes go last\n      }\n      return order.indexOf(category);\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        if (!node.attributes || node.attributes.length < 2) {\n          return;\n        }\n\n        const attributes = node.attributes.filter(\n          (attr) => attr.type === 'GlimmerAttrNode' && attr.name\n        );\n\n        for (let i = 1; i < attributes.length; i++) {\n          const current = attributes[i];\n          const currentIndex = getExpectedIndex(current.name);\n\n          for (let j = 0; j < i; j++) {\n            const previous = attributes[j];\n            const previousIndex = getExpectedIndex(previous.name);\n\n            if (currentIndex < previousIndex) {\n              context.report({\n                node: current,\n                messageId: 'wrongOrder',\n                data: {\n                  currentAttr: current.name,\n                  position: 'before',\n                  expectedAttr: previous.name,\n                },\n                fix(fixer) {\n                  const currentText = sourceCode.getText(current);\n                  const previousText = sourceCode.getText(previous);\n                  return [\n                    fixer.replaceTextRange(previous.range, currentText),\n                    fixer.replaceTextRange(current.range, previousText),\n                  ];\n                },\n              });\n              break;\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-block-indentation.js",
    "content": "'use strict';\n\nconst editorConfigUtil = require('../utils/editorconfig');\n\nconst VOID_TAGS = new Set([\n  'area',\n  'base',\n  'br',\n  'col',\n  'command',\n  'embed',\n  'hr',\n  'img',\n  'input',\n  'keygen',\n  'link',\n  'meta',\n  'param',\n  'source',\n  'track',\n  'wbr',\n]);\nconst IGNORED_ELEMENTS = new Set(['pre', 'script', 'style', 'textarea']);\n\nfunction isControlChar(char) {\n  return char === '~' || char === '{' || char === '}';\n}\n\nfunction getDisplayName(node) {\n  switch (node.type) {\n    case 'GlimmerElementNode': {\n      return `<${node.tag}>`;\n    }\n    case 'GlimmerBlockStatement': {\n      return `{{#${node.path.original}}}`;\n    }\n    case 'GlimmerMustacheStatement': {\n      return `{{${node.path.original}}}`;\n    }\n    case 'GlimmerTextNode': {\n      return node.chars.replace(/^\\s*/, '');\n    }\n    case 'GlimmerCommentStatement': {\n      return `<!--${node.value}-->`;\n    }\n    case 'GlimmerMustacheCommentStatement': {\n      return `{{!${node.value}}}`;\n    }\n    default: {\n      return node.path?.original || '';\n    }\n  }\n}\n\nfunction childrenFor(node) {\n  if (node.type === 'GlimmerBlockStatement') {\n    return node.program.body;\n  }\n  return node.children || [];\n}\n\nfunction hasChildren(node) {\n  return childrenFor(node).length > 0;\n}\n\nfunction hasLeadingContent(child, siblings) {\n  const currentIndex = siblings.indexOf(child);\n  for (let j = currentIndex - 1; j >= 0; j--) {\n    const sibling = siblings[j];\n    if (sibling.loc && sibling.type !== 'GlimmerTextNode') {\n      if (sibling.loc.end.line === child.loc.start.line) {\n        return true;\n      }\n      break;\n    } else if (sibling.type === 'GlimmerTextNode') {\n      const lines = sibling.chars.split(/[\\n\\r]/);\n      const lastLine = lines.at(-1);\n      if (lastLine.trim()) {\n        return true;\n      }\n      if (lines.length > 1) {\n        break;\n      }\n    }\n  }\n  return false;\n}\n\nfunction detectNestedElseIfBlock(node) {\n  const inverse = node.inverse;\n  const firstItem = inverse && inverse.body[0];\n  if (inverse && firstItem && firstItem.type === 'GlimmerBlockStatement') {\n    return (\n      inverse.loc.start.line === firstItem.loc.start.line &&\n      inverse.loc.start.column > firstItem.loc.start.column\n    );\n  }\n  return false;\n}\n\nfunction parseOptions(options) {\n  if (!options) {\n    return { indentation: 2 };\n  }\n  if (typeof options === 'number') {\n    return { indentation: options };\n  }\n  if (options === 'tab') {\n    return { indentation: 1 };\n  }\n  if (typeof options === 'object') {\n    return {\n      indentation: options.indentation || 2,\n      ignoreComments: options.ignoreComments || false,\n    };\n  }\n  return { indentation: 2 };\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce consistent indentation for block statements and their children',\n      category: 'Stylistic Issues',\n      recommended: false,\n      recommendedGjs: false,\n      recommendedGts: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-block-indentation.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        oneOf: [\n          { type: 'integer', minimum: 0 },\n          { enum: ['tab'] },\n          {\n            type: 'object',\n            properties: {\n              indentation: { type: 'integer', minimum: 0 },\n              ignoreComments: { type: 'boolean' },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      incorrectEnd:\n        'Incorrect indentation for `{{displayName}}` beginning at L{{startLine}}:C{{startColumn}}. Expected `{{display}}` ending at L{{endLine}}:C{{endColumn}} to be at an indentation of {{expectedColumn}}, but was found at {{actualColumn}}.',\n      incorrectChild:\n        'Incorrect indentation for `{{display}}` beginning at L{{startLine}}:C{{startColumn}}. Expected `{{display}}` to be at an indentation of {{expectedColumn}}, but was found at {{actualColumn}}.',\n      incorrectElse:\n        'Incorrect indentation for inverse block of `{{{{#{{displayName}}}}}}` beginning at L{{startLine}}:C{{startColumn}}. Expected `{{{{else}}}}` starting at L{{elseLine}}:C{{elseColumn}} to be at an indentation of {{expectedColumn}}, but was found at {{actualColumn}}.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/block-indentation.js',\n      docs: 'docs/rule/block-indentation.md',\n      tests: 'test/unit/rules/block-indentation-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0];\n    let config;\n    if (options === undefined) {\n      // No explicit config — try .editorconfig for indent_size\n      const filePath = context.filename;\n      const editorConfig = editorConfigUtil.resolveEditorConfig(filePath);\n      const indent = editorConfig.indent_size;\n      config = { indentation: typeof indent === 'number' ? indent : 2 };\n    } else {\n      config = parseOptions(options);\n    }\n    const sourceCode = context.sourceCode;\n    const sourceText = sourceCode.getText();\n    const sourceLines = sourceText.split('\\n');\n    const elementStack = [];\n    const seen = new Set();\n    const elseIfBlocks = new WeakSet();\n    let templateRange = null;\n\n    // Precompute line start offsets for fixing\n    const lineOffsets = [0];\n    for (const [i, char] of [...sourceText].entries()) {\n      if (char === '\\n') {\n        lineOffsets.push(i + 1);\n      }\n    }\n\n    /**\n     * Build a fix that replaces the leading whitespace of a line with the expected indentation.\n     */\n    function makeIndentFix(line, expectedColumn, actualColumn) {\n      const lineIndex = line - 1;\n      if (lineIndex < 0 || lineIndex >= lineOffsets.length) {\n        return undefined;\n      }\n      const lineStart = lineOffsets[lineIndex];\n      const lineText = sourceLines[lineIndex] ?? '';\n      const firstNonWhitespace = lineText.search(/\\S/);\n      if (firstNonWhitespace < 0) {\n        return undefined;\n      }\n      // Only fix if the reported column matches the line's leading whitespace\n      // (skip cases where the element is mid-line, e.g. content followed by </div>)\n      if (actualColumn !== undefined && firstNonWhitespace !== actualColumn) {\n        return undefined;\n      }\n      const indentChar = config.indentation === 1 ? '\\t' : ' ';\n      const expectedIndent = indentChar.repeat(expectedColumn);\n      return (fixer) =>\n        fixer.replaceTextRange([lineStart, lineStart + firstNonWhitespace], expectedIndent);\n    }\n\n    function isWithinIgnoredElement() {\n      return elementStack.some((n) => IGNORED_ELEMENTS.has(n.tag));\n    }\n\n    function endingControlCharCount(node) {\n      if (node.type === 'GlimmerElementNode') {\n        return 3; // </>\n      }\n\n      const nodeSource = sourceCode.getText(node);\n      const endingToken = `/${node.path.original}`;\n      const indexOfEnding = nodeSource.lastIndexOf(endingToken);\n\n      let leadingControlCharCount = 0;\n      let i = indexOfEnding - 1;\n      while (i >= 0 && isControlChar(nodeSource[i])) {\n        leadingControlCharCount++;\n        i--;\n      }\n\n      let trailingControlCharCount = 0;\n      i = indexOfEnding + endingToken.length;\n      while (i < nodeSource.length && isControlChar(nodeSource[i])) {\n        trailingControlCharCount++;\n        i++;\n      }\n\n      return leadingControlCharCount + 1 + trailingControlCharCount; // +1 for closing slash\n    }\n\n    function shouldValidateBlockEnd(node) {\n      if (elseIfBlocks.has(node)) {\n        return false;\n      }\n      if (node.type === 'GlimmerElementNode' && VOID_TAGS.has(node.tag)) {\n        return false;\n      }\n      if (isWithinIgnoredElement()) {\n        return false;\n      }\n      if (node.type === 'GlimmerElementNode') {\n        return hasChildren(node);\n      }\n      return true;\n    }\n\n    function validateBlockEnd(node) {\n      if (!shouldValidateBlockEnd(node)) {\n        return;\n      }\n\n      const isElement = node.type === 'GlimmerElementNode';\n      const displayName = isElement ? node.tag : node.path.original;\n      const display = isElement ? `</${displayName}>` : `{{/${displayName}}}`;\n      const startColumn = node.loc.start.column;\n      const endColumn = node.loc.end.column;\n\n      const controlCharCount = endingControlCharCount(node);\n      const correctedEndColumn = endColumn - displayName.length - controlCharCount;\n      const expectedEndColumn = startColumn;\n\n      if (correctedEndColumn !== expectedEndColumn) {\n        context.report({\n          node,\n          messageId: 'incorrectEnd',\n          loc: { line: node.loc.end.line, column: correctedEndColumn },\n          data: {\n            displayName,\n            display,\n            startLine: node.loc.start.line,\n            startColumn: node.loc.start.column,\n            endLine: node.loc.end.line,\n            endColumn: node.loc.end.column,\n            expectedColumn: expectedEndColumn,\n            actualColumn: correctedEndColumn,\n          },\n          fix: makeIndentFix(node.loc.end.line, expectedEndColumn, correctedEndColumn),\n        });\n      }\n    }\n\n    function validateBlockChildren(node) {\n      if (isWithinIgnoredElement()) {\n        return;\n      }\n\n      const children = childrenFor(node).filter((x) => !elseIfBlocks.has(x));\n\n      if (!hasChildren(node)) {\n        return;\n      }\n\n      // Blocks that start and end on the same line cannot have indentation issues\n      if (node.loc.start.line === node.loc.end.line) {\n        return;\n      }\n\n      const startColumn = node.loc.start.column;\n      const expectedStartColumn = startColumn + config.indentation;\n\n      for (const child of children) {\n        if (!child.loc) {\n          continue;\n        }\n\n        if (\n          config.ignoreComments &&\n          (child.type === 'GlimmerCommentStatement' ||\n            child.type === 'GlimmerMustacheCommentStatement')\n        ) {\n          break;\n        }\n\n        if (hasLeadingContent(child, children)) {\n          continue;\n        }\n\n        let childStartColumn = child.loc.start.column;\n        let childStartLine = child.loc.start.line;\n\n        // Sanitize text node starting column info\n        if (child.type === 'GlimmerTextNode') {\n          const withoutLeadingNewLines = child.chars.replace(/^(\\r\\n|\\n)*/, '');\n          const firstNonWhitespace = withoutLeadingNewLines.search(/\\S/);\n\n          // The TextNode is whitespace only, skip\n          if (firstNonWhitespace === -1) {\n            continue;\n          }\n\n          // Reset the child start column if there's a line break\n          if (/^(\\r\\n|\\n)/.test(child.chars)) {\n            childStartColumn = 0;\n            const newLineLength = child.chars.length - withoutLeadingNewLines.length;\n            const leadingNewLines = child.chars.slice(0, newLineLength);\n            childStartLine += (leadingNewLines.match(/\\n/g) || []).length;\n          }\n\n          childStartColumn += firstNonWhitespace;\n\n          // Detect if the TextNode starts with `{{`, correct for the stripped leading backslash\n          if (withoutLeadingNewLines.slice(0, 2) === '{{') {\n            childStartColumn -= 1;\n          }\n        }\n\n        if (expectedStartColumn !== childStartColumn) {\n          const display = getDisplayName(child);\n\n          context.report({\n            node,\n            messageId: 'incorrectChild',\n            loc: { line: childStartLine, column: childStartColumn },\n            data: {\n              display,\n              startLine: childStartLine,\n              startColumn: childStartColumn,\n              expectedColumn: expectedStartColumn,\n              actualColumn: childStartColumn,\n            },\n            fix: makeIndentFix(childStartLine, expectedStartColumn, childStartColumn),\n          });\n        }\n      }\n    }\n\n    function validateBlockElse(node) {\n      if (node.type !== 'GlimmerBlockStatement' || !node.inverse) {\n        return;\n      }\n\n      if (detectNestedElseIfBlock(node)) {\n        elseIfBlocks.add(node.inverse.body[0]);\n      }\n\n      const startColumn = node.loc.start.column;\n      const expectedStartColumn = startColumn;\n      const elseStartColumn = node.program.loc.end.column;\n\n      if (elseStartColumn !== expectedStartColumn) {\n        const displayName = node.path.original;\n\n        context.report({\n          node,\n          messageId: 'incorrectElse',\n          loc: { line: node.inverse.loc.start.line, column: elseStartColumn },\n          data: {\n            displayName,\n            startLine: node.loc.start.line,\n            startColumn: node.loc.start.column,\n            elseLine: node.inverse.loc.start.line,\n            elseColumn: elseStartColumn,\n            expectedColumn: expectedStartColumn,\n            actualColumn: elseStartColumn,\n            else: 'else',\n          },\n          fix: makeIndentFix(node.inverse.loc.start.line, expectedStartColumn, elseStartColumn),\n        });\n      }\n    }\n\n    function process(node) {\n      // Skip nodes that start and end on the same line\n      if (node.loc.start.line === node.loc.end.line || seen.has(node)) {\n        seen.add(node);\n        return;\n      }\n\n      validateBlockElse(node);\n      validateBlockEnd(node);\n      validateBlockChildren(node);\n\n      seen.add(node);\n    }\n\n    return {\n      GlimmerTemplate(node) {\n        // Track the template range so we can skip the wrapper element in GJS\n        templateRange = node.range;\n      },\n\n      GlimmerBlockStatement(node) {\n        process(node);\n      },\n\n      GlimmerElementNode(node) {\n        // Skip the <template> wrapper element in GJS mode.\n        // In GJS, the wrapper has tag='template' and same range as GlimmerTemplate.\n        // In HBS, root elements share the range but have their actual tag name.\n        if (\n          templateRange &&\n          node.tag === 'template' &&\n          node.range[0] === templateRange[0] &&\n          node.range[1] === templateRange[1]\n        ) {\n          return;\n        }\n        elementStack.push(node);\n        process(node);\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        if (\n          templateRange &&\n          node.tag === 'template' &&\n          node.range[0] === templateRange[0] &&\n          node.range[1] === templateRange[1]\n        ) {\n          return;\n        }\n        elementStack.pop();\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-builtin-component-arguments.js",
    "content": "const FORBIDDEN_ATTRIBUTES = {\n  Input: new Set(['checked', 'type', 'value']),\n  Textarea: new Set(['value']),\n};\n\nfunction generateErrorMessage(component, attribute) {\n  return `Setting the \\`${attribute}\\` attribute on the builtin <${component}> component is not allowed. Did you mean \\`@${attribute}\\`?`;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow setting certain attributes on builtin components',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-builtin-component-arguments.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/builtin-component-arguments.js',\n      docs: 'docs/rule/builtin-component-arguments.md',\n      tests: 'test/unit/rules/builtin-component-arguments-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // In GJS/GTS, track imports from @ember/component to distinguish\n    // Ember's built-in Input/Textarea from custom components with the same name.\n    // See https://github.com/ember-template-lint/ember-template-lint/issues/2786\n    const importedComponents = new Map();\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/component') {\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier') {\n              const original = specifier.imported.name;\n              if (original === 'Input' || original === 'Textarea') {\n                importedComponents.set(specifier.local.name, original);\n              }\n            }\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        const { tag, attributes } = node;\n\n        // In strict mode (GJS/GTS), only flag if the component was imported from @ember/component\n        if (isStrictMode) {\n          const original = importedComponents.get(tag);\n          if (!original) {\n            return;\n          }\n          const forbiddenAttributes = FORBIDDEN_ATTRIBUTES[original];\n          if (forbiddenAttributes && attributes) {\n            for (const attribute of attributes) {\n              if (attribute.name && forbiddenAttributes.has(attribute.name)) {\n                context.report({\n                  node: attribute,\n                  message: generateErrorMessage(original, attribute.name),\n                });\n              }\n            }\n          }\n          return;\n        }\n\n        // In loose mode (HBS), check by tag name directly\n        const forbiddenAttributes = FORBIDDEN_ATTRIBUTES[tag];\n        if (forbiddenAttributes && attributes) {\n          for (const attribute of attributes) {\n            if (attribute.name && forbiddenAttributes.has(attribute.name)) {\n              context.report({\n                node: attribute,\n                message: generateErrorMessage(tag, attribute.name),\n              });\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-deprecated-inline-view-helper.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow inline {{view}} helper',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-deprecated-inline-view-helper.md',\n      templateMode: 'loose',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      deprecated:\n        'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/deprecated-inline-view-helper.js',\n      docs: 'docs/rule/deprecated-inline-view-helper.md',\n      tests: 'test/unit/rules/deprecated-inline-view-helper-test.js',\n    },\n  },\n\n  create(context) {\n    const isStrictMode = context.filename.endsWith('.gjs') || context.filename.endsWith('.gts');\n    if (isStrictMode) {\n      return {};\n    }\n\n    const sourceCode = context.sourceCode;\n\n    // Track block param names to avoid false positives on locals like:\n    //   {{#each items as |view|}} {{view.name}} {{/each}}\n    const localScopes = [];\n\n    function pushLocals(params) {\n      localScopes.push(new Set(params || []));\n    }\n\n    function popLocals() {\n      localScopes.pop();\n    }\n\n    function isLocal(name) {\n      for (const scope of localScopes) {\n        if (scope.has(name)) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    function isViewPath(pathNode) {\n      return (\n        pathNode &&\n        pathNode.type === 'GlimmerPathExpression' &&\n        pathNode.original &&\n        pathNode.original.startsWith('view.') &&\n        pathNode.head?.type !== 'ThisHead' &&\n        pathNode.head?.type !== 'AtHead' &&\n        !isLocal('view')\n      );\n    }\n\n    function checkHashForViewPaths(node) {\n      if (node.hash && node.hash.pairs) {\n        // Skip {{yield}} invocations — yield hash pairs are not view references\n        const isYield =\n          node.path &&\n          node.path.type === 'GlimmerPathExpression' &&\n          node.path.original === 'yield' &&\n          node.path.head?.type !== 'ThisHead' &&\n          node.path.head?.type !== 'AtHead';\n        if (isYield) {\n          return false;\n        }\n\n        for (const pair of node.hash.pairs) {\n          // Skip hash pairs with key \"to\" (e.g., {{yield to=\"inverse\"}})\n          if (pair.key === 'to') {\n            continue;\n          }\n          if (isViewPath(pair.value)) {\n            const strippedValue = pair.value.original.replace('view.', '');\n            context.report({\n              node,\n              messageId: 'deprecated',\n              fix(fixer) {\n                return fixer.replaceText(pair.value, strippedValue);\n              },\n            });\n            return true;\n          }\n        }\n      }\n      return false;\n    }\n\n    function checkForView(node) {\n      if (node.path && node.path.type === 'GlimmerPathExpression') {\n        // Check for {{view ...}} with params or hash pairs\n        if (node.path.original === 'view' && !isLocal('view')) {\n          if (node.params && node.params.length > 0) {\n            // {{view 'component-name'}} with a single string param is fixable\n            const firstParam = node.params[0];\n            const isFixable =\n              node.params.length === 1 && firstParam.type === 'GlimmerStringLiteral';\n            context.report({\n              node,\n              messageId: 'deprecated',\n              fix: isFixable ? (fixer) => fixer.replaceText(node, `{{${firstParam.value}}}`) : null,\n            });\n            return;\n          }\n          if (node.hash && node.hash.pairs && node.hash.pairs.length > 0) {\n            context.report({\n              node,\n              messageId: 'deprecated',\n            });\n            return;\n          }\n        }\n        // Check for {{view.something}} paths\n        if (isViewPath(node.path)) {\n          const strippedPath = node.path.original.replace('view.', '');\n          context.report({\n            node,\n            messageId: 'deprecated',\n            fix(fixer) {\n              return fixer.replaceText(node.path, strippedPath);\n            },\n          });\n          return;\n        }\n      }\n      // Check hash values for view.* references\n      checkHashForViewPaths(node);\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        // Push this block's params into scope so children can detect locals,\n        // but check the block's own path/hash first (params aren't in scope yet\n        // for the block's own arguments in real Handlebars semantics).\n        checkForView(node);\n        if (node.program && node.program.blockParams) {\n          pushLocals(node.program.blockParams);\n        }\n      },\n      'GlimmerBlockStatement:exit'(node) {\n        if (node.program && node.program.blockParams) {\n          popLocals();\n        }\n      },\n\n      GlimmerElementNode(node) {\n        // Check element attributes for view.* references (e.g., <div class={{view.something}}>)\n        if (node.attributes) {\n          for (const attr of node.attributes) {\n            if (\n              attr.value &&\n              attr.value.type === 'GlimmerMustacheStatement' &&\n              isViewPath(attr.value.path)\n            ) {\n              const strippedValue = attr.value.path.original.replace('view.', '');\n              context.report({\n                node: attr.value,\n                messageId: 'deprecated',\n                fix(fixer) {\n                  return fixer.replaceText(attr.value.path, strippedValue);\n                },\n              });\n            }\n          }\n        }\n\n        if (node.blockParams && node.blockParams.length > 0) {\n          pushLocals(node.blockParams);\n        }\n      },\n      'GlimmerElementNode:exit'(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          popLocals();\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        // Skip mustache statements that are element attribute values;\n        // those are handled by the GlimmerElementNode visitor instead.\n        if (node.parent && node.parent.type === 'GlimmerAttrNode') {\n          return;\n        }\n        checkForView(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-deprecated-render-helper.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow {{render}} helper',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-deprecated-render-helper.md',\n      templateMode: 'loose',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      deprecated:\n        'The render helper is deprecated in favor of using components. See https://emberjs.com/deprecations/v2.x/#toc_code-render-code-helper',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/deprecated-render-helper.js',\n      docs: 'docs/rule/deprecated-render-helper.md',\n      tests: 'test/unit/rules/deprecated-render-helper-test.js',\n    },\n  },\n\n  create(context) {\n    const isStrictMode = context.filename.endsWith('.gjs') || context.filename.endsWith('.gts');\n    if (isStrictMode) {\n      return {};\n    }\n\n    const sourceCode = context.sourceCode;\n\n    function buildFix(node) {\n      const first = node.params[0];\n      if (!first || first.type !== 'GlimmerStringLiteral') {\n        return null;\n      }\n      const templateName = first.value;\n\n      if (node.params.length === 1) {\n        // {{render 'name'}} → {{name}}\n        return (fixer) => fixer.replaceText(node, `{{${templateName}}}`);\n      }\n\n      if (node.params.length === 2) {\n        // {{render 'name' model}} → {{name model=model}}\n        const model = sourceCode.getText(node.params[1]);\n        return (fixer) => fixer.replaceText(node, `{{${templateName} model=${model}}}`);\n      }\n\n      return null;\n    }\n\n    function checkForRender(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'render'\n      ) {\n        context.report({\n          node,\n          messageId: 'deprecated',\n          fix: buildFix(node),\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        checkForRender(node);\n      },\n\n      GlimmerBlockStatement(node) {\n        checkForRender(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-eol-last.js",
    "content": "'use strict';\n\nconst editorConfigUtil = require('../utils/editorconfig');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'require or disallow newline at the end of template files',\n      category: 'Stylistic Issues',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-eol-last.md',\n      templateMode: 'both',\n    },\n    fixable: 'whitespace',\n    schema: [\n      {\n        enum: ['always', 'never', 'editorconfig'],\n      },\n    ],\n    messages: {\n      mustEnd: 'template must end with newline',\n      mustNotEnd: 'template cannot end with newline',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/eol-last.js',\n      docs: 'docs/rule/eol-last.md',\n      tests: 'test/unit/rules/eol-last-test.js',\n    },\n  },\n\n  create(context) {\n    const option = context.options[0] || 'always';\n    let config = option;\n\n    if (option === 'editorconfig') {\n      const editorConfig = editorConfigUtil.resolveEditorConfig(context.filename);\n      const insertFinalNewline = editorConfig['insert_final_newline'];\n      if (typeof insertFinalNewline === 'boolean') {\n        config = insertFinalNewline ? 'always' : 'never';\n      } else {\n        throw new TypeError(\n          `The template-eol-last rule allows setting the configuration to \\`\"editorconfig\"\\` only when an \\`.editorconfig\\` file with the \\`insert_final_newline\\` setting exists.\\n\\nFound: ${JSON.stringify(editorConfig, null, 2)}`\n        );\n      }\n    }\n\n    const sourceCode = context.sourceCode;\n\n    return {\n      'GlimmerTemplate:exit'(node) {\n        if (node.body.length === 0) {\n          return;\n        }\n\n        // In gjs/gts mode, the template is wrapped in <template> tags — eol-last\n        // only applies to standalone .hbs files. File-level eol-last for gjs/gts\n        // is handled by the standard eslint eol-last rule.\n        const templateSource = sourceCode.getText(node);\n        if (templateSource.startsWith('<template>')) {\n          return;\n        }\n        const lastChar = templateSource.at(-1);\n\n        if (config === 'always' && lastChar !== '\\n') {\n          context.report({\n            node,\n            messageId: 'mustEnd',\n            fix(fixer) {\n              return fixer.insertTextAfter(node.body.at(-1), '\\n');\n            },\n          });\n        } else if (config === 'never' && lastChar === '\\n') {\n          const lastBody = node.body.at(-1);\n          context.report({\n            node,\n            messageId: 'mustNotEnd',\n            fix(fixer) {\n              // Trailing newline may be inside the last text node or in a gap after the last body node\n              if (lastBody.type === 'GlimmerTextNode' && lastBody.chars.endsWith('\\n')) {\n                const text = sourceCode.getText(lastBody);\n                return fixer.replaceText(lastBody, text.replace(/\\n$/, ''));\n              }\n              // Trailing newline is after the last body node (e.g., after <img> or </div>)\n              return fixer.removeRange([node.range[1] - 1, node.range[1]]);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-linebreak-style.js",
    "content": "'use strict';\n\nconst os = require('node:os');\nconst editorConfigUtil = require('../utils/editorconfig');\n\nfunction toDisplay(value) {\n  return value.replaceAll('\\r', 'CR').replaceAll('\\n', 'LF');\n}\n\nconst EOL_MAP = { lf: '\\n', cr: '\\r', crlf: '\\r\\n' };\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce consistent linebreaks in templates',\n      category: 'Stylistic Issues',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-linebreak-style.md',\n      templateMode: 'both',\n    },\n    fixable: 'whitespace',\n    schema: [\n      {\n        enum: ['unix', 'windows', 'system'],\n      },\n    ],\n    messages: {\n      wrongLinebreak: 'Wrong linebreak used. Expected {{expected}} but found {{found}}.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/linebreak-style.js',\n      docs: 'docs/rule/linebreak-style.md',\n      tests: 'test/unit/rules/linebreak-style-test.js',\n    },\n  },\n\n  create(context) {\n    const option = context.options[0] || 'unix';\n\n    // editorconfig end_of_line takes precedence over the rule option\n    const editorConfig = editorConfigUtil.resolveEditorConfig(context.filename);\n    const editorConfigEol = editorConfig['end_of_line'];\n\n    let expectedLinebreak;\n    if (editorConfigEol && EOL_MAP[editorConfigEol]) {\n      expectedLinebreak = EOL_MAP[editorConfigEol];\n    } else if (option === 'system') {\n      expectedLinebreak = os.EOL;\n    } else if (option === 'windows') {\n      expectedLinebreak = '\\r\\n';\n    } else {\n      expectedLinebreak = '\\n';\n    }\n\n    const sourceCode = context.sourceCode;\n\n    return {\n      'GlimmerTemplate:exit'(node) {\n        const text = sourceCode.getText(node);\n        const re = /\\r\\n?|\\n/g;\n        let match;\n\n        while ((match = re.exec(text)) !== null) {\n          const found = match[0];\n          if (found !== expectedLinebreak) {\n            const startIndex = node.range[0] + match.index;\n            context.report({\n              loc: {\n                start: sourceCode.getLocFromIndex(startIndex),\n                end: sourceCode.getLocFromIndex(startIndex + found.length),\n              },\n              messageId: 'wrongLinebreak',\n              data: {\n                expected: toDisplay(expectedLinebreak),\n                found: toDisplay(found),\n              },\n              fix(fixer) {\n                return fixer.replaceTextRange(\n                  [startIndex, startIndex + found.length],\n                  expectedLinebreak\n                );\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-link-href-attributes.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require href attribute on link elements',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-link-href-attributes.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      missingHref:\n        '<a> elements must have an href attribute. Use <button> for clickable elements that are not links.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/link-href-attributes.js',\n      docs: 'docs/rule/link-href-attributes.md',\n      tests: 'test/unit/rules/link-href-attributes-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'a') {\n          return;\n        }\n\n        const hasHref = node.attributes?.some((attr) => attr.name === 'href');\n\n        if (!hasHref) {\n          // Exception: <a> with both role and aria-disabled doesn't need href\n          const hasRole = node.attributes?.some((attr) => attr.name === 'role');\n          const hasAriaDisabled = node.attributes?.some((attr) => attr.name === 'aria-disabled');\n          if (hasRole && hasAriaDisabled) {\n            return;\n          }\n\n          context.report({\n            node,\n            messageId: 'missingHref',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-link-rel-noopener.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require rel=\"noopener noreferrer\" on links with target=\"_blank\"',\n      category: 'Security',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-link-rel-noopener.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      missingRel: 'links with target=\"_blank\" must have rel=\"noopener noreferrer\"',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/link-rel-noopener.js',\n      docs: 'docs/rule/link-rel-noopener.md',\n      tests: 'test/unit/rules/link-rel-noopener-test.js',\n    },\n  },\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'a') {\n          return;\n        }\n\n        const targetAttr = node.attributes?.find((a) => a.name === 'target');\n        if (!targetAttr?.value || targetAttr.value.type !== 'GlimmerTextNode') {\n          return;\n        }\n        if (targetAttr.value.chars !== '_blank') {\n          return;\n        }\n\n        const relAttr = node.attributes?.find((a) => a.name === 'rel');\n        const relValue = relAttr?.value?.type === 'GlimmerTextNode' ? relAttr.value.chars : '';\n        const hasNoopener = /(?:^|\\s)noopener(?:\\s|$)/.test(relValue);\n        const hasNoreferrer = /(?:^|\\s)noreferrer(?:\\s|$)/.test(relValue);\n        const hasProperRel = hasNoopener && hasNoreferrer;\n\n        if (!hasProperRel) {\n          context.report({\n            node: targetAttr,\n            messageId: 'missingRel',\n            fix(fixer) {\n              if (relAttr && relAttr.value?.type === 'GlimmerTextNode') {\n                // Strip existing noopener/noreferrer tokens, then re-add in canonical order\n                const oldValue = relAttr.value.chars.trim().replaceAll(/\\s+/g, ' ');\n                const filtered = oldValue\n                  .split(' ')\n                  .filter((t) => t !== 'noopener' && t !== 'noreferrer')\n                  .join(' ');\n                const newValue = `${filtered} noopener noreferrer`.trim();\n                return fixer.replaceText(relAttr.value, `\"${newValue}\"`);\n              }\n              // No rel attribute — insert one before the closing >\n              const sourceCode = context.sourceCode;\n              const openTag = sourceCode.getText(node).match(/^<a[^>]*/)[0];\n              const insertPos = node.range[0] + openTag.length;\n              return fixer.insertTextBeforeRange(\n                [insertPos, insertPos],\n                ' rel=\"noopener noreferrer\"'\n              );\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-modifier-name-case.js",
    "content": "/* eslint-disable unicorn/consistent-function-scoping */\n\nconst SIMPLE_DASHERIZE_REGEXP = /[A-Z]/g;\nconst ALPHA = /[A-Za-z]/;\n\nfunction dasherize(key) {\n  return key\n    .replaceAll(SIMPLE_DASHERIZE_REGEXP, (char, index) => {\n      if (index === 0 || !ALPHA.test(key[index - 1])) {\n        return char.toLowerCase();\n      }\n      return `-${char.toLowerCase()}`;\n    })\n    .replaceAll('::', '/');\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require dasherized names for modifiers',\n      category: 'Stylistic Issues',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-modifier-name-case.md',\n      templateMode: 'loose',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      dasherized:\n        'Use dasherized names for modifier invocation. Please replace `{{modifierName}}` with `{{dasherizedName}}`.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/modifier-name-case.js',\n      docs: 'docs/rule/modifier-name-case.md',\n      tests: 'test/unit/rules/modifier-name-case-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    if (!filename.endsWith('.hbs')) {\n      return {};\n    }\n\n    function isModifierHelper(node) {\n      return (\n        node.path && node.path.type === 'GlimmerPathExpression' && node.path.original === 'modifier'\n      );\n    }\n\n    return {\n      GlimmerElementModifierStatement(node) {\n        const modifierName = node.path?.original;\n\n        if (typeof modifierName === 'string' && modifierName !== dasherize(modifierName)) {\n          const dasherizedName = dasherize(modifierName);\n          context.report({\n            node,\n            messageId: 'dasherized',\n            data: { modifierName, dasherizedName },\n            fix(fixer) {\n              return fixer.replaceTextRange(node.path.range, dasherizedName);\n            },\n          });\n        }\n      },\n\n      GlimmerSubExpression(node) {\n        if (!isModifierHelper(node)) {\n          return;\n        }\n\n        const nameParam = node.params?.[0];\n\n        if (nameParam && nameParam.type === 'GlimmerStringLiteral') {\n          const modifierName = nameParam.value;\n\n          if (typeof modifierName === 'string' && modifierName !== dasherize(modifierName)) {\n            const dasherizedName = dasherize(modifierName);\n            context.report({\n              node: nameParam,\n              messageId: 'dasherized',\n              data: { modifierName, dasherizedName },\n              fix(fixer) {\n                return fixer.replaceTextRange(nameParam.range, `\"${dasherizedName}\"`);\n              },\n            });\n          }\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (!isModifierHelper(node)) {\n          return;\n        }\n\n        const nameParam = node.params?.[0];\n\n        if (nameParam && nameParam.type === 'GlimmerStringLiteral') {\n          const modifierName = nameParam.value;\n\n          if (typeof modifierName === 'string' && modifierName !== dasherize(modifierName)) {\n            const dasherizedName = dasherize(modifierName);\n            context.report({\n              node: nameParam,\n              messageId: 'dasherized',\n              data: { modifierName, dasherizedName },\n              fix(fixer) {\n                return fixer.replaceTextRange(nameParam.range, `\"${dasherizedName}\"`);\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n/* eslint-enable unicorn/consistent-function-scoping */\n"
  },
  {
    "path": "lib/rules/template-no-abstract-roles.js",
    "content": "const { roles } = require('aria-query');\n\n// Abstract ARIA roles, sourced from aria-query. Abstract roles exist only for\n// taxonomy — authors should never set role=\"widget\" / \"landmark\" / etc.\n// https://www.w3.org/TR/wai-aria-1.2/#abstract_roles\nconst PROHIBITED_ROLE_VALUES = new Set(\n  [...roles.keys()].filter((role) => roles.get(role).abstract)\n);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow abstract ARIA roles',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-abstract-roles.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      abstractRole:\n        '{{role}} is an abstract role, and is not a valid value for the role attribute.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-abstract-roles.js',\n      docs: 'docs/rule/no-abstract-roles.md',\n      tests: 'test/unit/rules/no-abstract-roles-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n\n        if (roleAttr && roleAttr.value && roleAttr.value.type === 'GlimmerTextNode') {\n          const roleValue = roleAttr.value.chars;\n\n          if (PROHIBITED_ROLE_VALUES.has(roleValue)) {\n            context.report({\n              node: roleAttr,\n              messageId: 'abstractRole',\n              data: { role: roleValue },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-accesskey-attribute.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow accesskey attribute',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-accesskey-attribute.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noAccesskey:\n        'No access key attribute allowed. Inconsistencies between keyboard shortcuts and keyboard commands used by screenreader and keyboard only users create accessibility complications.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-accesskey-attribute.js',\n      docs: 'docs/rule/no-accesskey-attribute.md',\n      tests: 'test/unit/rules/no-accesskey-attribute-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const accesskeyAttr = node.attributes?.find((attr) => attr.name === 'accesskey');\n\n        if (accesskeyAttr) {\n          context.report({\n            node: accesskeyAttr,\n            messageId: 'noAccesskey',\n            fix(fixer) {\n              // Remove the attribute including preceding whitespace\n              const sourceCode = context.sourceCode;\n              const text = sourceCode.getText();\n              const attrStart = accesskeyAttr.range[0];\n              const attrEnd = accesskeyAttr.range[1];\n\n              let removeStart = attrStart;\n              while (removeStart > 0 && /\\s/.test(text[removeStart - 1])) {\n                removeStart--;\n              }\n\n              return fixer.removeRange([removeStart, attrEnd]);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-action-modifiers.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of {{action}} modifiers',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-action-modifiers.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        oneOf: [\n          {\n            type: 'array',\n            items: { type: 'string' },\n            uniqueItems: true,\n          },\n          {\n            type: 'object',\n            properties: {\n              allowlist: {\n                type: 'array',\n                items: { type: 'string' },\n                uniqueItems: true,\n              },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      noActionModifier: 'Do not use action modifiers. Use on modifier with a function instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-action-modifiers.js',\n      docs: 'docs/rule/no-action-modifiers.md',\n      tests: 'test/unit/rules/no-action-modifiers-test.js',\n    },\n  },\n\n  create(context) {\n    const firstOption = context.options[0];\n    const allowlist = Array.isArray(firstOption) ? firstOption : firstOption?.allowlist || [];\n    const sourceCode = context.sourceCode;\n\n    function checkForActionModifier(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'action' &&\n        node.path.head?.type !== 'AtHead' &&\n        node.path.head?.type !== 'ThisHead'\n      ) {\n        // Only offer autofix when the first param is a path expression.\n        // If hash pairs are present, only fix when the sole hash pair is `on=\"<event>\"` —\n        // we can read the event name from there and drop the pair in the output.\n        const maybePath = node.params?.[0];\n        const hashPairs = node.hash?.pairs || [];\n        const onPair = hashPairs.find((p) => p.key === 'on');\n        const otherPairs = hashPairs.filter((p) => p.key !== 'on');\n\n        const canFix =\n          maybePath &&\n          maybePath.type === 'GlimmerPathExpression' &&\n          otherPairs.length === 0 &&\n          (onPair === undefined || onPair.value.type === 'GlimmerStringLiteral');\n\n        context.report({\n          node,\n          messageId: 'noActionModifier',\n          fix: canFix\n            ? (fixer) => {\n                const eventName =\n                  onPair && onPair.value.type === 'GlimmerStringLiteral'\n                    ? onPair.value.value\n                    : 'click';\n\n                const args = node.params.slice(1);\n                const pathText = sourceCode.getText(maybePath);\n\n                let replacement;\n                if (args.length === 0) {\n                  // {{action this.handleClick}} → {{on \"click\" this.handleClick}}\n                  // {{action this.handleClick on=\"submit\"}} → {{on \"submit\" this.handleClick}}\n                  replacement = `on \"${eventName}\" ${pathText}`;\n                } else {\n                  // {{action this.handleClick \"arg\"}} → {{on \"click\" (fn this.handleClick \"arg\")}}\n                  const argsText = args.map((a) => sourceCode.getText(a)).join(' ');\n                  replacement = `on \"${eventName}\" (fn ${pathText} ${argsText})`;\n                }\n\n                // Replace from start of `action` to just before `}}`, covering any hash pairs\n                return fixer.replaceTextRange([node.path.range[0], node.range[1] - 2], replacement);\n              }\n            : null,\n        });\n      }\n    }\n\n    return {\n      GlimmerElementModifierStatement(node) {\n        const parent = node.parent;\n        if (parent && parent.type === 'GlimmerElementNode' && allowlist.includes(parent.tag)) {\n          return;\n        }\n        checkForActionModifier(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-action-on-submit-button.js",
    "content": "function isInsideForm(node) {\n  let current = node.parent;\n  while (current) {\n    if (current.type === 'GlimmerElementNode' && current.tag === 'form') {\n      // <form method=\"dialog\"> closes the dialog on submit without navigating,\n      // so click handlers on submit buttons are intentional and correct.\n      // See https://github.com/ember-template-lint/ember-template-lint/issues/2989\n      const methodAttr = current.attributes?.find((a) => a.name === 'method');\n      if (\n        methodAttr &&\n        methodAttr.value?.type === 'GlimmerTextNode' &&\n        methodAttr.value.chars.toLowerCase() === 'dialog'\n      ) {\n        return false;\n      }\n      return true;\n    }\n    current = current.parent;\n  }\n  return false;\n}\n\nfunction isSubmitButton(node) {\n  for (const attr of node.attributes || []) {\n    if (\n      attr.name === 'type' &&\n      attr.value?.type === 'GlimmerTextNode' &&\n      attr.value.chars !== 'submit'\n    ) {\n      return false;\n    }\n  }\n  return true;\n}\n\nfunction hasClickHandlingModifier(node) {\n  for (const mod of node.modifiers || []) {\n    if (mod.path?.original === 'action') {\n      // {{action ...}} defaults to click event\n      const onPair = mod.hash?.pairs?.find((p) => p.key === 'on');\n      if (!onPair) {\n        return true;\n      }\n      const eventValue = onPair.value?.value ?? onPair.value?.chars;\n      if (eventValue === 'click') {\n        return true;\n      }\n    }\n    if (mod.path?.original === 'on') {\n      // {{on \"event\" handler}}\n      if (mod.params?.length > 0 && mod.params[0].value === 'click') {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow action attribute on submit buttons',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-action-on-submit-button.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noActionOnSubmitButton:\n        'In a `<form>`, a `<button>` with `type=\"submit\"` should have no click action',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-action-on-submit-button.js',\n      docs: 'docs/rule/no-action-on-submit-button.md',\n      tests: 'test/unit/rules/no-action-on-submit-button-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'button') {\n          return;\n        }\n\n        if (!isInsideForm(node)) {\n          return;\n        }\n\n        if (isSubmitButton(node) && hasClickHandlingModifier(node)) {\n          context.report({\n            node,\n            messageId: 'noActionOnSubmitButton',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-action.js",
    "content": "function isActionHelperPath(node) {\n  if (!node.path || node.path.type !== 'GlimmerPathExpression') {\n    return false;\n  }\n\n  // Check if it's the action helper (not this.action or @action)\n  const path = node.path;\n  if (path.original !== 'action') {\n    return false;\n  }\n  // Avoid deprecated data/this properties — those are not the helper.\n  const head = path.head;\n  if (head && (head.type === 'AtHead' || head.type === 'ThisHead')) {\n    return false;\n  }\n  return true;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow {{action}} helper',\n      category: 'Deprecations',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-action.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      subExpression:\n        'Do not use `action` as (action ...) — deprecated in Ember 5.9, removed in 6.0. Use the `fn` helper instead.',\n      mustache:\n        'Do not use `action` in templates — deprecated in Ember 5.9, removed in 6.0. Use the `on` modifier and `fn` helper instead.',\n      modifier:\n        'Do not use `action` as an element modifier — deprecated in Ember 5.9, removed in 6.0. Use the `on` modifier instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-action.js',\n      docs: 'docs/rule/no-action.md',\n      tests: 'test/unit/rules/no-action-test.js',\n    },\n  },\n\n  create(context) {\n    // `action` is an ambient strict-mode keyword in Ember (registered in\n    // STRICT_MODE_KEYWORDS), so `{{action this.x}}` works in .gjs/.gts without\n    // an import. Still flag the ambient keyword everywhere — but skip when\n    // `action` resolves to a binding (JS import/const, or template block param).\n    // ember-eslint-parser already registers template block params in scope, so\n    // a single getScope walk covers both.\n    const sourceCode = context.sourceCode;\n\n    function isInScope(node) {\n      if (!sourceCode) {\n        return false;\n      }\n      try {\n        let scope = sourceCode.getScope(node);\n        while (scope) {\n          if (scope.variables.some((v) => v.name === 'action')) {\n            return true;\n          }\n          scope = scope.upper;\n        }\n      } catch {\n        // getScope not available in .hbs-only mode\n      }\n      return false;\n    }\n\n    function shouldFlag(node) {\n      return isActionHelperPath(node) && !isInScope(node);\n    }\n\n    return {\n      GlimmerSubExpression(node) {\n        if (shouldFlag(node)) {\n          context.report({ node, messageId: 'subExpression' });\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (shouldFlag(node)) {\n          context.report({ node, messageId: 'mustache' });\n        }\n      },\n\n      GlimmerElementModifierStatement(node) {\n        if (shouldFlag(node)) {\n          context.report({ node, messageId: 'modifier' });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-args-paths.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow args.foo paths in templates, use @foo instead',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-args-paths.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      argsPath:\n        'Component templates should avoid \"{{path}}\" usage, try \"@{{replacement}}\" instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-args-paths.js',\n      docs: 'docs/rule/no-args-paths.md',\n      tests: 'test/unit/rules/no-args-paths-test.js',\n    },\n  },\n  create(context) {\n    const localScopes = [];\n\n    function pushLocals(params) {\n      localScopes.push(new Set(params || []));\n    }\n\n    function popLocals() {\n      localScopes.pop();\n    }\n\n    function isLocal(name) {\n      for (const scope of localScopes) {\n        if (scope.has(name)) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        if (node.program && node.program.blockParams) {\n          pushLocals(node.program.blockParams);\n        }\n      },\n      'GlimmerBlockStatement:exit'(node) {\n        if (node.program && node.program.blockParams) {\n          popLocals();\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          pushLocals(node.blockParams);\n        }\n      },\n      'GlimmerElementNode:exit'(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          popLocals();\n        }\n      },\n\n      GlimmerPathExpression(node) {\n        const path = node.original;\n\n        // @args.foo is a valid named argument — skip paths starting with @\n        if (node.head?.type === 'AtHead') {\n          return;\n        }\n\n        if (!path?.startsWith('args.') && !path?.startsWith('this.args.')) {\n          return;\n        }\n\n        // Skip when 'args' is a block param in the current scope\n        if (isLocal('args')) {\n          return;\n        }\n\n        const replacement = path.replace(/^(this\\.)?args\\./, '');\n\n        context.report({\n          node,\n          messageId: 'argsPath',\n          data: { path, replacement },\n          fix(fixer) {\n            return fixer.replaceText(node, `@${replacement}`);\n          },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-arguments-for-html-elements.js",
    "content": "const { isNativeElement } = require('../utils/is-native-element');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow @arguments on HTML elements',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-arguments-for-html-elements.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noArgumentsForHtmlElements:\n        '@arguments can only be used on components, not HTML elements. Use regular attributes instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-arguments-for-html-elements.js',\n      docs: 'docs/rule/no-arguments-for-html-elements.md',\n      tests: 'test/unit/rules/no-arguments-for-html-elements-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerElementNode(node) {\n        if (!isNativeElement(node, sourceCode)) {\n          return;\n        }\n\n        for (const attr of node.attributes) {\n          if (attr.type === 'GlimmerAttrNode' && attr.name.startsWith('@')) {\n            context.report({\n              node: attr,\n              messageId: 'noArgumentsForHtmlElements',\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-aria-hidden-body.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow aria-hidden on body element',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-aria-hidden-body.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noAriaHiddenBody:\n        'The aria-hidden attribute should never be present on the <body> element, as it hides the entire document from assistive technology',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-aria-hidden-body.js',\n      docs: 'docs/rule/no-aria-hidden-body.md',\n      tests: 'test/unit/rules/no-aria-hidden-body-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag === 'body') {\n          const ariaHiddenAttr = node.attributes?.find((attr) => attr.name === 'aria-hidden');\n\n          if (ariaHiddenAttr) {\n            context.report({\n              node: ariaHiddenAttr,\n              messageId: 'noAriaHiddenBody',\n              fix(fixer) {\n                const sourceCode = context.sourceCode;\n                const text = sourceCode.getText();\n                const attrStart = ariaHiddenAttr.range[0];\n                const attrEnd = ariaHiddenAttr.range[1];\n\n                let removeStart = attrStart;\n                while (removeStart > 0 && /\\s/.test(text[removeStart - 1])) {\n                  removeStart--;\n                }\n\n                return fixer.removeRange([removeStart, attrEnd]);\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-aria-unsupported-elements.js",
    "content": "const { dom } = require('aria-query');\n\n// HTML elements that don't support ARIA — sourced from aria-query's dom map,\n// which marks spec-reserved elements with .reserved = true. Matches the authoritative\n// list in the HTML-AAM spec (https://www.w3.org/TR/html-aria/#docconformance).\nconst ELEMENTS_WITHOUT_ARIA_SUPPORT = new Set(\n  [...dom].filter(([, def]) => def.reserved).map(([tag]) => tag)\n);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'disallow ARIA roles, states, and properties on elements that do not support them',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-aria-unsupported-elements.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unsupported:\n        'ARIA attribute \"{{attribute}}\" is not supported on <{{element}}> elements. Consider using a different element.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-aria-unsupported-elements.js',\n      docs: 'docs/rule/no-aria-unsupported-elements.md',\n      tests: 'test/unit/rules/no-aria-unsupported-elements-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (ELEMENTS_WITHOUT_ARIA_SUPPORT.has(node.tag)) {\n          const ariaAttributes = node.attributes?.filter(\n            (attr) =>\n              attr.type === 'GlimmerAttrNode' &&\n              attr.name &&\n              (attr.name.startsWith('aria-') || attr.name === 'role')\n          );\n\n          for (const attr of ariaAttributes || []) {\n            context.report({\n              node: attr,\n              messageId: 'unsupported',\n              data: {\n                attribute: attr.name,\n                element: node.tag,\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-array-prototype-extensions.js",
    "content": "const FIRST_OBJECT_PROP_NAME = 'firstObject';\nconst LAST_OBJECT_PROP_NAME = 'lastObject';\n\nconst ERROR_MESSAGES = {\n  LAST_OBJECT: 'Array prototype extension property lastObject usage is disallowed.',\n  FIRST_OBJECT:\n    \"Array prototype extension property firstObject usage is disallowed. Please use Ember's get helper instead, e.g. `(get @list '0')`.\",\n};\n\n/**\n * Check if the path should be allowed. `@firstObject.test`, `@lastObject`, and\n * `this.firstObject` are allowed (they are property names, not extensions).\n */\nfunction isAllowed(originalStr, matchedStr) {\n  // allow `@firstObject.test`, `@lastObject`\n  if (originalStr.startsWith(`@${matchedStr}`)) {\n    return true;\n  }\n\n  const originalParts = originalStr.split('.');\n  const matchStrIndex = originalParts.indexOf(matchedStr);\n\n  // if not found\n  if (matchStrIndex === -1) {\n    return true;\n  }\n  // allow this.firstObject (direct property, not extension)\n  return !matchStrIndex || originalParts[matchStrIndex - 1] === 'this';\n}\n\n/**\n * Check if current node is a `get` helper and its string literal contains matchedStr.\n * For example `{{get this 'list.firstObject'}}` returns true,\n * but `{{get this 'firstObject'}}` returns false (that's a direct property).\n */\nfunction isGetHelperWithMatchedLiteral(node, matchedStr) {\n  if (node.original !== 'get') {\n    return false;\n  }\n\n  const parent = node.parent;\n  if (\n    parent &&\n    (parent.type === 'GlimmerMustacheStatement' || parent.type === 'GlimmerSubExpression') &&\n    parent.params &&\n    parent.params[1] &&\n    parent.params[1].type === 'GlimmerStringLiteral'\n  ) {\n    const literal = parent.params[1].value || parent.params[1].original;\n    const parts = literal.split('.');\n    const matchStrIndex = parts.indexOf(matchedStr);\n\n    // matchedStr is found and not the `{{get this 'firstObject'}}` case\n    return (\n      matchStrIndex !== -1 &&\n      !(matchStrIndex === 0 && parent.params[0] && parent.params[0].original === 'this')\n    );\n  }\n  return false;\n}\n\n/**\n * Build a `get` helper call string from a path containing `firstObject`.\n * e.g. \"this.items.firstObject\" => { target: \"this.items\", key: \"0\" }\n * e.g. \"this.items.firstObject.name\" => { target: \"this.items\", key: \"0.name\" }\n */\nfunction buildGetHelperParts(originalPath) {\n  const parts = originalPath.split('.');\n  const firstObjectIndex = parts.indexOf(FIRST_OBJECT_PROP_NAME);\n  const target = parts.slice(0, firstObjectIndex).join('.');\n  const afterParts = ['0', ...parts.slice(firstObjectIndex + 1)];\n  const key = afterParts.join('.');\n  return { target, key };\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of Ember Array prototype extensions',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-array-prototype-extensions.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      lastObject: ERROR_MESSAGES.LAST_OBJECT,\n      firstObject: ERROR_MESSAGES.FIRST_OBJECT,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-array-prototype-extensions.js',\n      docs: 'docs/rule/no-array-prototype-extensions.md',\n      tests: 'test/unit/rules/no-array-prototype-extensions-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerPathExpression(node) {\n        if (!node.original) {\n          return;\n        }\n\n        // Handle lastObject — no fixer available\n        if (\n          !isAllowed(node.original, LAST_OBJECT_PROP_NAME) ||\n          isGetHelperWithMatchedLiteral(node, LAST_OBJECT_PROP_NAME)\n        ) {\n          context.report({\n            node,\n            messageId: 'lastObject',\n          });\n        }\n\n        // Handle firstObject — with autofix\n        const isGetWithFirstObject = isGetHelperWithMatchedLiteral(node, FIRST_OBJECT_PROP_NAME);\n\n        if (!isAllowed(node.original, FIRST_OBJECT_PROP_NAME) || isGetWithFirstObject) {\n          context.report({\n            node,\n            messageId: 'firstObject',\n            fix(fixer) {\n              const parent = node.parent;\n\n              // Case 1: get helper with firstObject in string literal\n              // e.g. {{get @list \"items.firstObject\"}} => {{get @list \"items.0\"}}\n              if (isGetWithFirstObject) {\n                const literalNode = parent.params[1];\n                const literalText = sourceCode.getText(literalNode);\n                // Detect quote character used in source\n                const quote = literalText[0];\n                const literalValue = literalNode.value || literalNode.original;\n                const newValue = literalValue\n                  .split('.')\n                  .map((part) => (part === FIRST_OBJECT_PROP_NAME ? '0' : part))\n                  .join('.');\n                return fixer.replaceText(literalNode, `${quote}${newValue}${quote}`);\n              }\n\n              // Case 2: PathExpression is the path of a MustacheStatement\n              // e.g. {{this.items.firstObject}} => {{get this.items \"0\"}}\n              // e.g. {{this.items.firstObject.name}} => {{get this.items \"0.name\"}}\n              if (parent && parent.type === 'GlimmerMustacheStatement' && parent.path === node) {\n                const { target, key } = buildGetHelperParts(node.original);\n                const newText = `{{get ${target} \"${key}\"}}`;\n                return fixer.replaceText(parent, newText);\n              }\n\n              // Case 3: PathExpression used as a subexpression argument or other context\n              // e.g. {{helper this.items.firstObject}} => {{helper (get this.items \"0\")}}\n              if (parent) {\n                const { target, key } = buildGetHelperParts(node.original);\n                const newText = `(get ${target} \"${key}\")`;\n                return fixer.replaceText(node, newText);\n              }\n\n              return null;\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-at-ember-render-modifiers.js",
    "content": "// Sub-path → canonical kebab-case modifier name (default import).\nconst RENDER_MODIFIER_IMPORT_PATHS = {\n  '@ember/render-modifiers/modifiers/did-insert': 'did-insert',\n  '@ember/render-modifiers/modifiers/did-update': 'did-update',\n  '@ember/render-modifiers/modifiers/will-destroy': 'will-destroy',\n};\n\n// Named exports of the root package `@ember/render-modifiers` → canonical kebab name.\nconst ROOT_NAMED_EXPORTS = {\n  didInsert: 'did-insert',\n  didUpdate: 'did-update',\n  willDestroy: 'will-destroy',\n};\n\nconst KEBAB_NAMES = new Set(['did-insert', 'did-update', 'will-destroy']);\nconst ROOT_PACKAGE = '@ember/render-modifiers';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of @ember/render-modifiers',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-at-ember-render-modifiers.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noRenderModifier:\n        'Do not use the `{{modifier}}` modifier. This modifier was intended to ease migration to Octane and not for long-term side-effects. Instead, either refactor to use data derivation patterns for a performance boost, or refactor to use a custom modifier. See https://github.com/ember-modifier/ember-modifier',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-at-ember-render-modifiers.js',\n      docs: 'docs/rule/no-at-ember-render-modifiers.md',\n      tests: 'test/unit/rules/no-at-ember-render-modifiers-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // local import name → canonical kebab name. Only populated in GJS/GTS.\n    const importedModifiers = new Map();\n\n    function isRenderModifier(name) {\n      if (isStrictMode) {\n        return importedModifiers.has(name);\n      }\n      // HBS: resolver-resolved by canonical kebab name\n      return KEBAB_NAMES.has(name);\n    }\n\n    function canonicalName(name) {\n      return importedModifiers.get(name) || name;\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (!isStrictMode) {\n          return;\n        }\n        const source = node.source.value;\n\n        if (source === ROOT_PACKAGE) {\n          // `import { didInsert, didUpdate as x } from '@ember/render-modifiers'`\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier') {\n              const exportedName = specifier.imported.name;\n              const canonical = ROOT_NAMED_EXPORTS[exportedName];\n              if (canonical) {\n                importedModifiers.set(specifier.local.name, canonical);\n              }\n            }\n          }\n          return;\n        }\n\n        // Sub-path: `import didInsert from '@ember/render-modifiers/modifiers/did-insert'`\n        const canonical = RENDER_MODIFIER_IMPORT_PATHS[source];\n        if (!canonical) {\n          return;\n        }\n        for (const specifier of node.specifiers) {\n          if (specifier.type === 'ImportDefaultSpecifier') {\n            importedModifiers.set(specifier.local.name, canonical);\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (!node.modifiers) {\n          return;\n        }\n\n        for (const modifier of node.modifiers) {\n          if (modifier.path?.type !== 'GlimmerPathExpression') {\n            continue;\n          }\n          const name = modifier.path.original;\n          if (isRenderModifier(name)) {\n            context.report({\n              node: modifier,\n              messageId: 'noRenderModifier',\n              data: { modifier: canonicalName(name) },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-attrs-in-components.js",
    "content": "// Matches traditional, pod, ui/components, and -components/ paths.\n// Also matches Octane co-located templates (app/components/foo.hbs) via\n// /components/ — cf. ember-template-lint#1445.\nconst COMPONENT_TEMPLATE_REGEX = new RegExp(\n  'templates/components|components/.*/template|ui/components|-components/|/components/'\n);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow attrs in component templates',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-attrs-in-components.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noAttrs: 'Component templates should not contain `attrs`.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-attrs-in-components.js',\n      docs: 'docs/rule/no-attrs-in-components.md',\n      tests: 'test/unit/rules/no-attrs-in-components-test.js',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-attrs-in-components.js',\n      docs: 'docs/rule/no-attrs-in-components.md',\n      tests: 'test/unit/rules/no-attrs-in-components-test.js',\n    },\n  },\n  create(context) {\n    if (!COMPONENT_TEMPLATE_REGEX.test(context.filename)) {\n      return {};\n    }\n    return {\n      GlimmerPathExpression(node) {\n        // Flag `attrs.*` and `this.attrs.*` — both are pre-Octane args-leakage\n        // patterns from @ember/component. In the Glimmer AST, `this.attrs.foo`\n        // has parts[0] === 'attrs' (this is the receiver, not a part).\n        if (node.parts && node.parts[0] === 'attrs') {\n          context.report({ node, messageId: 'noAttrs' });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-autofocus-attribute.js",
    "content": "/**\n * `autofocus` is a boolean HTML attribute. Per the HTML spec, any presence\n * (including `autofocus=\"false\"`, `autofocus=\"\"`, `autofocus=\"autofocus\"`)\n * means the element will auto-focus. Only the genuine absence of the\n * attribute turns off auto-focus.\n *\n * jsx-a11y's `no-autofocus` treats `autofocus={false}` / `autofocus=\"false\"`\n * as opt-outs — that is a peer-plugin convention that diverges from HTML\n * boolean-attribute semantics. vue-a11y and lit-a11y are presence-based,\n * consistent with the spec. We follow the spec.\n *\n * The only exception is a boolean-literal `false` — in element syntax\n * written as `autofocus={{false}}`, and in mustache hash syntax written as\n * `{{input autofocus=false}}`. Both forms express intent to omit the\n * attribute conditionally, and the rendered HTML will have no autofocus\n * attribute. Treat both literal-false cases as opt-out.\n *\n * Verified against Glimmer VM's attribute-normalization source:\n * glimmer-vm/packages/@glimmer/runtime/lib/vm/attributes/dynamic.ts —\n * `normalizeValue` returns `null` for `false | undefined | null`, and\n * `SimpleDynamicAttribute.update()` calls `element.removeAttribute(name)`\n * when the normalized value is null. So `autofocus={{false}}` renders\n * with the attribute entirely absent from the DOM.\n */\nfunction isMustacheBooleanFalse(value) {\n  if (value?.type !== 'GlimmerMustacheStatement') {\n    return false;\n  }\n  const expr = value.path;\n  return expr?.type === 'GlimmerBooleanLiteral' && expr.value === false;\n}\n\n/**\n * Returns true when the given node (a GlimmerElementNode OR a\n * GlimmerMustacheStatement, e.g. `{{input autofocus=true}}`) is a `<dialog>`\n * element or is nested (at any depth) inside a `<dialog>` element. Per MDN,\n * autofocus on (or within) a dialog is recommended because a dialog should\n * focus its initial element when opened.\n *\n * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog\n */\n// Returns true for `{{input ...}}` and `{{component \"input\" ...}}` mustache\n// invocations — the only built-ins that deterministically render a native\n// <input> with forwarded attributes.\nfunction isNativeInputHelper(node) {\n  const path = node.path;\n  if (!path) {\n    return false;\n  }\n  // Direct invocation: `{{input ...}}`.\n  if (path.type === 'GlimmerPathExpression' && path.original === 'input') {\n    return true;\n  }\n  // Contextual component: `{{component \"input\" ...}}`.\n  if (path.type === 'GlimmerPathExpression' && path.original === 'component') {\n    const firstParam = node.params && node.params[0];\n    if (firstParam && firstParam.type === 'GlimmerStringLiteral' && firstParam.value === 'input') {\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction isInsideDialog(node) {\n  if (node.type === 'GlimmerElementNode' && node.tag === 'dialog') {\n    return true;\n  }\n  let ancestor = node.parent;\n  while (ancestor) {\n    if (ancestor.type === 'GlimmerElementNode' && ancestor.tag === 'dialog') {\n      return true;\n    }\n    ancestor = ancestor.parent;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow autofocus attribute',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-autofocus-attribute.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noAutofocus:\n        'Avoid using autofocus attribute. Autofocusing elements can cause usability issues for sighted and non-sighted users.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-autofocus-attribute.js',\n      docs: 'docs/rule/no-autofocus-attribute.md',\n      tests: 'test/unit/rules/no-autofocus-attribute-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const autofocusAttr = node.attributes?.find((attr) => attr.name === 'autofocus');\n\n        if (!autofocusAttr) {\n          return;\n        }\n\n        // Mustache boolean-literal `autofocus={{false}}` renders no attribute\n        // at all — the only statically-known opt-out consistent with HTML\n        // boolean-attribute semantics.\n        if (isMustacheBooleanFalse(autofocusAttr.value)) {\n          return;\n        }\n\n        // MDN dialog exception: autofocus on a <dialog> or inside a <dialog>\n        // is recommended behavior, not an accessibility defect.\n        if (isInsideDialog(node)) {\n          return;\n        }\n\n        context.report({\n          node: autofocusAttr,\n          messageId: 'noAutofocus',\n          fix(fixer) {\n            const sourceCode = context.sourceCode;\n            const text = sourceCode.getText();\n            const attrStart = autofocusAttr.range[0];\n            const attrEnd = autofocusAttr.range[1];\n\n            let removeStart = attrStart;\n            while (removeStart > 0 && /\\s/.test(text[removeStart - 1])) {\n              removeStart--;\n            }\n\n            return fixer.removeRange([removeStart, attrEnd]);\n          },\n        });\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (!node.hash || !node.hash.pairs) {\n          return;\n        }\n        const autofocusPair = node.hash.pairs.find((pair) => pair.key === 'autofocus');\n        if (!autofocusPair) {\n          return;\n        }\n\n        // Narrow to helpers that deterministically render a native `autofocus`\n        // attribute. The rule's purpose is the HTML attribute; arbitrary\n        // components taking an `autofocus` prop are opaque — we can't tell\n        // statically whether that prop forwards to HTML or is used for\n        // something else.\n        //   - `{{input ...}}` — Ember's classic input helper renders a native\n        //     <input> with forwarded attributes.\n        //   - `{{component \"input\" ...}}` — contextual component resolution\n        //     points at the same helper.\n        //\n        // FUTURE: when type-aware linting lands (e.g., Glint integration or\n        // a template-type-check step), we can resolve custom components that\n        // forward `autofocus` to a native <input> and flag those too. For now\n        // we stay conservative to avoid false positives on unrelated helpers\n        // that happen to take an `autofocus` prop.\n        if (!isNativeInputHelper(node)) {\n          return;\n        }\n\n        // Mustache hash-pair `{{input autofocus=false}}` — boolean literal\n        // false at the hash-pair level is unambiguous and renders no attr.\n        // Note: `autofocus=\"false\"` in mustache syntax IS still flagged — per\n        // HTML boolean-attribute semantics the string \"false\" is truthy; it\n        // is only jsx-a11y that carves that form out.\n        const pairValue = autofocusPair.value;\n        if (pairValue?.type === 'GlimmerBooleanLiteral' && pairValue.value === false) {\n          return;\n        }\n\n        // MDN dialog exception: autofocus on a mustache component/helper\n        // nested inside a <dialog> is recommended behavior, not a defect.\n        if (isInsideDialog(node)) {\n          return;\n        }\n\n        context.report({\n          node: autofocusPair,\n          messageId: 'noAutofocus',\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-bare-strings.js",
    "content": "const DEFAULT_GLOBAL_ATTRIBUTES = [\n  'title',\n  'aria-label',\n  'aria-placeholder',\n  'aria-roledescription',\n  'aria-valuetext',\n];\n\nconst DEFAULT_ELEMENT_ATTRIBUTES = {\n  input: ['placeholder'],\n  img: ['alt'],\n};\n\nconst BUILTIN_COMPONENT_ATTRIBUTES = {\n  Input: ['placeholder', '@placeholder'],\n  Textarea: ['placeholder', '@placeholder'],\n};\n\nconst DEFAULT_ALLOWLIST = [\n  '&lpar;',\n  '&rpar;',\n  '&comma;',\n  '&period;',\n  '&amp;',\n  '&AMP;',\n  '&plus;',\n  '&minus;',\n  '&equals;',\n  '&times;',\n  '&ast;',\n  '&midast;',\n  '&sol;',\n  '&num;',\n  '&percnt;',\n  '&excl;',\n  '&quest;',\n  '&colon;',\n  '&lsqb;',\n  '&lbrack;',\n  '&rsqb;',\n  '&rbrack;',\n  '&lcub;',\n  '&lbrace;',\n  '&rcub;',\n  '&rbrace;',\n  '&lt;',\n  '&LT;',\n  '&gt;',\n  '&GT;',\n  '&bull;',\n  '&bullet;',\n  '&mdash;',\n  '&ndash;',\n  '&nbsp;',\n  '&Tab;',\n  '&NewLine;',\n  '&verbar;',\n  '&vert;',\n  '&VerticalLine;',\n  '(',\n  ')',\n  ',',\n  '.',\n  '&',\n  '+',\n  '-',\n  '=',\n  '*',\n  '/',\n  '#',\n  '%',\n  '!',\n  '?',\n  ':',\n  '[',\n  ']',\n  '{',\n  '}',\n  '<',\n  '>',\n  '•',\n  '—',\n  ' ',\n  '|',\n];\n\nconst IGNORED_ELEMENTS = ['pre', 'script', 'style', 'template', 'textarea'];\n\nfunction sanitizeConfigArray(arr = []) {\n  return arr.filter((o) => o !== '').sort((a, b) => b.length - a.length);\n}\n\nfunction mergeObjects(obj1 = {}, obj2 = {}) {\n  const result = {};\n  for (const [key, value] of Object.entries(obj1)) {\n    result[key] = [...(result[key] || []), ...value];\n  }\n  for (const [key, value] of Object.entries(obj2)) {\n    result[key] = [...(result[key] || []), ...value];\n  }\n  return result;\n}\n\nfunction isPageTitleHelper(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'page-title';\n}\n\nfunction isIfHelper(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'if';\n}\n\nfunction isUnlessHelper(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'unless';\n}\n\nfunction isStringOnlyConcatHelper(node) {\n  return (\n    node.path?.type === 'GlimmerPathExpression' &&\n    node.path.original === 'concat' &&\n    (node.params || []).every((p) => p.type === 'GlimmerStringLiteral')\n  );\n}\n\nfunction isInAttrNode(node) {\n  let p = node.parent;\n  while (p) {\n    if (p.type === 'GlimmerAttrNode') {\n      return p;\n    }\n    p = p.parent;\n  }\n  return null;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow bare strings in templates (require translation/localization)',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-bare-strings.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        oneOf: [\n          {\n            type: 'object',\n            properties: {\n              allowlist: { type: 'array', items: { type: 'string' } },\n              globalAttributes: { type: 'array', items: { type: 'string' } },\n              elementAttributes: { type: 'object' },\n              ignoredElements: { type: 'array', items: { type: 'string' } },\n            },\n            additionalProperties: false,\n          },\n          {\n            type: 'array',\n            items: { type: 'string' },\n          },\n        ],\n      },\n    ],\n    messages: {\n      bareString: 'Non-translated string used{{additionalDescription}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-bare-strings.js',\n      docs: 'docs/rule/no-bare-strings.md',\n      tests: 'test/unit/rules/no-bare-strings-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    const rawConfig = context.options[0];\n    let config;\n\n    // In strict mode (gjs/gts), Input/Textarea could be custom components —\n    // prefer false negatives over false positives, matching ember-template-lint.\n    const defaultElementAttributes = isStrictMode\n      ? DEFAULT_ELEMENT_ATTRIBUTES\n      : mergeObjects(DEFAULT_ELEMENT_ATTRIBUTES, BUILTIN_COMPONENT_ATTRIBUTES);\n\n    if (Array.isArray(rawConfig)) {\n      config = {\n        allowlist: sanitizeConfigArray([...rawConfig, ...DEFAULT_ALLOWLIST]),\n        globalAttributes: [...DEFAULT_GLOBAL_ATTRIBUTES],\n        elementAttributes: defaultElementAttributes,\n        ignoredElements: [...IGNORED_ELEMENTS],\n      };\n    } else if (rawConfig && typeof rawConfig === 'object') {\n      config = {\n        allowlist: sanitizeConfigArray([...(rawConfig.allowlist || []), ...DEFAULT_ALLOWLIST]),\n        globalAttributes: [...(rawConfig.globalAttributes || []), ...DEFAULT_GLOBAL_ATTRIBUTES],\n        elementAttributes: mergeObjects(rawConfig.elementAttributes, defaultElementAttributes),\n        ignoredElements: [\n          ...sanitizeConfigArray(rawConfig.ignoredElements || []),\n          ...IGNORED_ELEMENTS,\n        ],\n      };\n    } else {\n      config = {\n        allowlist: [...DEFAULT_ALLOWLIST],\n        globalAttributes: [...DEFAULT_GLOBAL_ATTRIBUTES],\n        elementAttributes: defaultElementAttributes,\n        ignoredElements: [...IGNORED_ELEMENTS],\n      };\n    }\n\n    const elementStack = [];\n\n    function isWithinIgnoredElement() {\n      return elementStack.some((tag) => config.ignoredElements.includes(tag));\n    }\n\n    function getBareString(str) {\n      let s = str;\n      for (const entry of config.allowlist) {\n        while (s.includes(entry)) {\n          s = s.replace(entry, '');\n        }\n      }\n      return s.trim() === '' ? null : str;\n    }\n\n    function checkAndLog(node, additionalDescription) {\n      if (isWithinIgnoredElement()) {\n        return;\n      }\n\n      switch (node.type) {\n        case 'GlimmerTextNode': {\n          const bareString = getBareString(node.chars);\n          if (bareString) {\n            context.report({\n              node,\n              messageId: 'bareString',\n              data: { additionalDescription },\n            });\n          }\n          break;\n        }\n        case 'GlimmerConcatStatement': {\n          for (const part of node.parts || []) {\n            checkAndLog(part, additionalDescription);\n          }\n          break;\n        }\n        case 'GlimmerStringLiteral': {\n          const bareString = getBareString(node.value || '');\n          if (bareString) {\n            context.report({\n              node,\n              messageId: 'bareString',\n              data: { additionalDescription },\n            });\n          }\n          break;\n        }\n        default: {\n          break;\n        }\n      }\n    }\n\n    let currentElementNode = null;\n    let templateRange = null;\n\n    return {\n      GlimmerTemplate(node) {\n        // Only track the template range in GJS/GTS mode (not HBS mode).\n        // In GJS/GTS, the outermost <template> is a structural wrapper that should\n        // NOT be treated as an ignored HTML <template> element.\n        // In HBS mode, any <template> element is a real HTML element.\n        const isHbsParser = context.parserPath && context.parserPath.includes('hbs-parser');\n        if (!isHbsParser) {\n          templateRange = node.range;\n        }\n      },\n\n      GlimmerElementNode(node) {\n        currentElementNode = node;\n\n        // Skip the structural <template> wrapper in GJS/GTS mode.\n        // In GJS/GTS, the wrapper has tag='template' and same range as GlimmerTemplate.\n        // A real HTML <template> element will have a different range.\n        const isGjsWrapper =\n          templateRange &&\n          node.tag === 'template' &&\n          node.range[0] === templateRange[0] &&\n          node.range[1] === templateRange[1];\n\n        if (!isGjsWrapper) {\n          elementStack.push(node.tag);\n        }\n      },\n      'GlimmerElementNode:exit'(node) {\n        const isGjsWrapper =\n          templateRange &&\n          node.tag === 'template' &&\n          node.range[0] === templateRange[0] &&\n          node.range[1] === templateRange[1];\n\n        if (!isGjsWrapper) {\n          elementStack.pop();\n        }\n      },\n\n      GlimmerTextNode(node) {\n        if (!node.loc) {\n          return;\n        }\n\n        const attrParent = isInAttrNode(node);\n        if (attrParent) {\n          // Check if this attribute should be checked\n          const attrName = attrParent.name;\n          const tag = currentElementNode?.tag;\n          const isGlobal = config.globalAttributes.includes(attrName);\n          const isElement =\n            tag &&\n            config.elementAttributes[tag] &&\n            config.elementAttributes[tag].includes(attrName);\n\n          if (isGlobal || isElement) {\n            const desc = ` in \\`${attrName}\\` ${attrName.startsWith('@') ? 'argument' : 'attribute'}`;\n            checkAndLog(node, desc);\n          }\n        } else {\n          checkAndLog(node, '');\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        const inAttr = isInAttrNode(node);\n\n        // Check the path itself (StringLiteral path)\n        if (!inAttr && node.path) {\n          checkAndLog(node.path, '');\n        }\n\n        if (isPageTitleHelper(node)) {\n          for (const param of node.params || []) {\n            checkAndLog(param, '');\n          }\n        } else if (isIfHelper(node) && !inAttr) {\n          const [, maybeTrue, maybeFalse] = node.params || [];\n          if (maybeTrue) {\n            checkAndLog(maybeTrue, '');\n          }\n          if (maybeFalse) {\n            checkAndLog(maybeFalse, '');\n          }\n        } else if (isUnlessHelper(node) && !inAttr) {\n          const [, maybeFalse, maybeTrue] = node.params || [];\n          if (maybeTrue) {\n            checkAndLog(maybeTrue, '');\n          }\n          if (maybeFalse) {\n            checkAndLog(maybeFalse, '');\n          }\n        } else if (isStringOnlyConcatHelper(node) && !inAttr) {\n          if (node.params?.[0]) {\n            checkAndLog(node.params[0], '');\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-bare-yield.js",
    "content": "function isEmptyNode(node) {\n  return (\n    node.type === 'GlimmerMustacheCommentStatement' ||\n    node.type === 'GlimmerCommentStatement' ||\n    (node.type === 'GlimmerTextNode' && !node.chars.trim())\n  );\n}\n\nfunction isBareYield(node) {\n  return (\n    node.type === 'GlimmerMustacheStatement' &&\n    node.path.original === 'yield' &&\n    (!node.params || node.params.length === 0)\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow templates whose only meaningful content is a bare {{yield}}',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-bare-yield.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noBareYield: '{{yieldCall}}-only templates are not allowed.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-yield-only.js',\n      docs: 'docs/rule/no-yield-only.md',\n      tests: 'test/unit/rules/no-yield-only-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerTemplate(node) {\n        // In GJS/GTS mode the content lives inside a GlimmerElementNode wrapper\n        // with tag=\"template\"; in HBS mode the body is the content directly.\n        let body = node.body;\n        if (\n          body.length === 1 &&\n          body[0].type === 'GlimmerElementNode' &&\n          body[0].tag === 'template'\n        ) {\n          body = body[0].children;\n        }\n\n        const nonEmptyNodes = body.filter((n) => !isEmptyNode(n));\n        if (nonEmptyNodes.length === 1 && isBareYield(nonEmptyNodes[0])) {\n          context.report({\n            node: nonEmptyNodes[0],\n            messageId: 'noBareYield',\n            data: { yieldCall: '{{yield}}' },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-block-params-for-html-elements.js",
    "content": "const { isNativeElement } = require('../utils/is-native-element');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow block params on HTML elements',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-block-params-for-html-elements.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noBlockParamsForHtmlElements:\n        'Block params can only be used with components, not HTML elements.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-block-params-for-html-elements.js',\n      docs: 'docs/rule/no-block-params-for-html-elements.md',\n      tests: 'test/unit/rules/no-block-params-for-html-elements-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerElementNode(node) {\n        if (!isNativeElement(node, sourceCode)) {\n          return;\n        }\n\n        if (node.blockParams && node.blockParams.length > 0) {\n          context.report({\n            node,\n            messageId: 'noBlockParamsForHtmlElements',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-builtin-form-components.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of built-in form components',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-builtin-form-components.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noInput:\n        'Do not use the `Input` component. Built-in form components use two-way binding to mutate values. Instead, refactor to use a native HTML element.',\n      noTextarea:\n        'Do not use the `Textarea` component. Built-in form components use two-way binding to mutate values. Instead, refactor to use a native HTML element.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-builtin-form-components.js',\n      docs: 'docs/rule/no-builtin-form-components.md',\n      tests: 'test/unit/rules/no-builtin-form-components-test.js',\n    },\n  },\n\n  create(context) {\n    const MESSAGE_IDS = {\n      Input: 'noInput',\n      Textarea: 'noTextarea',\n    };\n\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // local name → original name ('Input' | 'Textarea')\n    // Only populated in GJS/GTS files via ImportDeclaration\n    const importedComponents = new Map();\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/component') {\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier') {\n              const original = specifier.imported.name;\n              if (original === 'Input' || original === 'Textarea') {\n                importedComponents.set(specifier.local.name, original);\n              }\n            }\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        const tag = node.tag;\n        if (isStrictMode) {\n          // In GJS/GTS: only flag if explicitly imported from @ember/component\n          const original = importedComponents.get(tag);\n          if (original) {\n            context.report({ node, messageId: MESSAGE_IDS[original] });\n          }\n        } else {\n          // In HBS: flag by canonical name (no import context available)\n          const messageId = MESSAGE_IDS[tag];\n          if (messageId) {\n            context.report({ node, messageId });\n          }\n        }\n      },\n\n      // Catch usages as a value: {{yield Input}}, (component Input), @field={{Input}}, etc.\n      GlimmerPathExpression(node) {\n        const name = node.original;\n        if (isStrictMode) {\n          const original = importedComponents.get(name);\n          if (original) {\n            context.report({ node, messageId: MESSAGE_IDS[original] });\n          }\n        } else {\n          const messageId = MESSAGE_IDS[name];\n          if (messageId) {\n            context.report({ node, messageId });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-capital-arguments.js",
    "content": "const RESERVED = new Set(['@arguments', '@args', '@block', '@else']);\nconst ALLOWED_PREFIX = /^[a-z]/;\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow capital arguments (use lowercase @arg instead of @Arg)',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-capital-arguments.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noCapitalArguments:\n        'Argument names should start with lowercase. Use @{{lowercase}} instead of @{{name}}.',\n      reservedArgument: '{{name}} is a reserved argument name, try to use another.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-capital-arguments.js',\n      docs: 'docs/rule/no-capital-arguments.md',\n      tests: 'test/unit/rules/no-capital-arguments-test.js',\n    },\n  },\n\n  create(context) {\n    function checkArgName(node, name) {\n      if (!name || !name.startsWith('@')) {\n        return;\n      }\n\n      const part = name.slice(1);\n      const firstChar = part.charAt(0);\n\n      if (RESERVED.has(name)) {\n        context.report({\n          node,\n          messageId: 'reservedArgument',\n          data: { name },\n        });\n      } else if (!ALLOWED_PREFIX.test(firstChar)) {\n        const lowercase = `@${firstChar.toLowerCase()}${part.slice(1)}`;\n        context.report({\n          node,\n          messageId: 'noCapitalArguments',\n          data: { name, lowercase },\n        });\n      }\n    }\n\n    return {\n      GlimmerPathExpression(node) {\n        const name = node.original || (node.head && (node.head.name || node.head));\n        if (typeof name === 'string' && name.startsWith('@')) {\n          checkArgName(node, name);\n        }\n      },\n      GlimmerAttrNode(node) {\n        if (node.name && node.name.startsWith('@')) {\n          checkArgName(node, node.name);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-chained-this.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow redundant `this.this` in templates',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-chained-this.md',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noChainedThis:\n        'this.this.* is not allowed in templates. This is likely a mistake — remove the redundant this.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-chained-this.js',\n      docs: 'docs/rule/no-chained-this.md',\n      tests: 'test/unit/rules/no-chained-this-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerPathExpression(node) {\n        const text = sourceCode.getText(node);\n        if (!text.startsWith('this.this.')) {\n          return;\n        }\n\n        const fixed = text.replace('this.this.', 'this.');\n\n        context.report({\n          node,\n          messageId: 'noChainedThis',\n          fix(fixer) {\n            const fixes = [fixer.replaceText(node, fixed)];\n\n            // Block statements have a closing tag path that must match\n            const parent = node.parent;\n            if (parent?.type === 'GlimmerBlockStatement' && parent.path === node) {\n              const closingPathEnd = parent.range[1] - 2; // before '}}'\n              const closingPathStart = closingPathEnd - text.length;\n              fixes.push(fixer.replaceTextRange([closingPathStart, closingPathEnd], fixed));\n            }\n\n            return fixes;\n          },\n        });\n      },\n      GlimmerElementNode(node) {\n        if (!node.tag?.startsWith('this.this.')) {\n          return;\n        }\n\n        const fixedTag = node.tag.replace('this.this.', 'this.');\n\n        context.report({\n          node,\n          messageId: 'noChainedThis',\n          fix(fixer) {\n            const fixes = [];\n\n            // Replace the tag name after '<'\n            const openStart = node.range[0] + 1;\n            fixes.push(fixer.replaceTextRange([openStart, openStart + node.tag.length], fixedTag));\n\n            // For non-self-closing elements, also fix the closing tag </tag>\n            if (!node.selfClosing) {\n              const closingEnd = node.range[1] - 1; // before '>'\n              const closingStart = closingEnd - node.tag.length; // start of tag name in </tag>\n              fixes.push(fixer.replaceTextRange([closingStart, closingEnd], fixedTag));\n            }\n\n            return fixes;\n          },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-class-bindings.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow passing classBinding or classNameBindings as arguments in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-class-bindings.md',\n      templateMode: 'loose',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noClassBindings:\n        'Passing the `{{name}}` property as an argument within templates is not allowed.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-class-bindings.js',\n      docs: 'docs/rule/no-class-bindings.md',\n      tests: 'test/unit/rules/no-class-bindings-test.js',\n    },\n  },\n\n  create(context) {\n    const isStrictMode = context.filename.endsWith('.gjs') || context.filename.endsWith('.gts');\n    if (isStrictMode) {\n      return {};\n    }\n\n    const FORBIDDEN_ATTR_NAMES = new Set([\n      'classBinding',\n      '@classBinding',\n      'classNameBindings',\n      '@classNameBindings',\n    ]);\n\n    const FORBIDDEN_HASH_KEYS = new Set(['classBinding', 'classNameBindings']);\n\n    return {\n      'GlimmerElementNode > GlimmerAttrNode'(node) {\n        if (FORBIDDEN_ATTR_NAMES.has(node.name)) {\n          context.report({\n            node,\n            messageId: 'noClassBindings',\n            data: { name: node.name },\n          });\n        }\n      },\n      GlimmerHashPair(node) {\n        if (FORBIDDEN_HASH_KEYS.has(node.key)) {\n          context.report({\n            node,\n            messageId: 'noClassBindings',\n            data: { name: node.key },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-curly-component-invocation.js",
    "content": "/* eslint-disable eslint-plugin/prefer-placeholders */\nconst BUILT_INS = new Set([\n  'action',\n  'array',\n  'component',\n  'concat',\n  'debugger',\n  'each',\n  'each-in',\n  'fn',\n  'get',\n  'hasBlock',\n  'has-block',\n  'has-block-params',\n  'hash',\n  'if',\n  'input',\n  'let',\n  'link-to',\n  'loc',\n  'log',\n  'mount',\n  'mut',\n  'on',\n  'outlet',\n  'partial',\n  'query-params',\n  'textarea',\n  'unbound',\n  'unique-id',\n  'unless',\n  'with',\n  '-in-element',\n  'in-element',\n  'app-version',\n  'rootURL',\n]);\n\nconst ALWAYS_CURLY = new Set(['yield']);\n\nfunction transformTagName(name, isLocal) {\n  // Preserve this.*, @*, and local variable names as-is\n  if (name.startsWith('@') || name.startsWith('this.') || isLocal) {\n    return name;\n  }\n\n  // Convert kebab-case to PascalCase for angle bracket syntax\n  const parts = name.split('/');\n  return parts\n    .map((part) => {\n      return part\n        .split('-')\n        .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n        .join('');\n    })\n    .join('::');\n}\n\nfunction parseConfig(config) {\n  const defaults = {\n    allow: [],\n    disallow: [],\n    requireDash: false,\n    noImplicitThis: true,\n  };\n\n  if (config === true) {\n    return defaults;\n  }\n\n  return { ...defaults, ...config };\n}\n\nfunction isExplicitThisPath(pathOriginal) {\n  return (\n    pathOriginal === 'this' || pathOriginal.startsWith('this.') || pathOriginal.startsWith('@')\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow curly component invocation, use angle bracket syntax instead',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-curly-component-invocation.md',\n      templateMode: 'loose',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allow: {\n            type: 'array',\n            items: { type: 'string' },\n          },\n          disallow: {\n            type: 'array',\n            items: { type: 'string' },\n          },\n          requireDash: {\n            type: 'boolean',\n          },\n          noImplicitThis: {\n            type: 'boolean',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-curly-component-invocation.js',\n      docs: 'docs/rule/no-curly-component-invocation.md',\n      tests: 'test/unit/rules/no-curly-component-invocation-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseConfig(context.options[0]);\n    const sourceCode = context.sourceCode;\n\n    /**\n     * Build a fix function for block statement curly→angle bracket conversion.\n     */\n    function buildBlockFix(node, angleBracketName) {\n      return function fix(fixer) {\n        // Convert hash pairs to @key attributes\n        const attrs = (node.hash?.pairs ?? []).map((pair) => {\n          const valueText = sourceCode.getText(pair.value);\n          if (pair.value.type === 'GlimmerStringLiteral') {\n            return `@${pair.key}=\"${pair.value.value}\"`;\n          }\n          return `@${pair.key}={{${valueText}}}`;\n        });\n\n        // Get block params\n        const blockParams = node.program?.blockParams ?? [];\n        const blockParamsStr = blockParams.length > 0 ? ` as |${blockParams.join(' ')}|` : '';\n\n        // Get body content\n        const bodyText = node.program?.body\n          ? node.program.body.map((n) => sourceCode.getText(n)).join('')\n          : '';\n\n        const attrStr = attrs.length > 0 ? ` ${attrs.join(' ')}` : '';\n        return fixer.replaceText(\n          node,\n          `<${angleBracketName}${attrStr}${blockParamsStr}>${bodyText}</${angleBracketName}>`\n        );\n      };\n    }\n\n    // Stack of block-param name arrays, one entry per active GlimmerBlockStatement or GlimmerElementNode.\n    const blockParamStack = [];\n    let insideAttrNode = false;\n\n    function isLocalVar(name) {\n      return blockParamStack.some((params) => params.includes(name));\n    }\n\n    /**\n     * Returns true if the mustache/block node's path head resolves to a\n     * JavaScript scope binding (import, const, function param, etc.) —\n     * i.e. the template reference is an explicit GJS/GTS binding, not an\n     * implicit resolver lookup. Such references should not be flagged as\n     * ambiguous curly invocations. Walks `scope.variables` up the chain by\n     * name, which handles Glimmer built-in names that don't appear in\n     * `scope.references`.\n     */\n    function isJsScopeBinding(node) {\n      const name = node.path?.original?.split('.')[0];\n      if (!sourceCode || !name) {\n        return false;\n      }\n      try {\n        let scope = sourceCode.getScope(node);\n        while (scope) {\n          if (scope.variables.some((v) => v.name === name)) {\n            return true;\n          }\n          scope = scope.upper;\n        }\n      } catch {\n        // sourceCode.getScope may not be available in .hbs-only mode; ignore.\n      }\n      return false;\n    }\n\n    function reportMustache(node, pathOriginal) {\n      const angleBracketName = transformTagName(pathOriginal);\n      context.report({\n        node,\n        message: `You are using the component {{${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \\`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\\`.`,\n      });\n    }\n\n    function checkMustacheWithNamedArgs(node, pathOriginal, explicitThis, local) {\n      // {{foo.bar bar=baz}} - multi-part path (not this./@ prefix) with named args\n      if (!explicitThis && pathOriginal.includes('.')) {\n        reportMustache(node, pathOriginal);\n        return;\n      }\n\n      // Explicit JS scope binding or block param — user already imported/scoped\n      // the value by that exact name; converting to angle-bracket would break.\n      if (local) {\n        return;\n      }\n\n      if (config.allow.includes(pathOriginal)) {\n        return;\n      }\n\n      // input/textarea with hash pairs are always reported\n      if (['input', 'textarea'].includes(pathOriginal)) {\n        reportMustache(node, pathOriginal);\n        return;\n      }\n\n      // requireDash: skip single-word names without a dash\n      if (config.requireDash && !pathOriginal.includes('-')) {\n        return;\n      }\n\n      // Built-in helpers with hash pairs are not reported\n      if (BUILT_INS.has(pathOriginal)) {\n        return;\n      }\n\n      reportMustache(node, pathOriginal);\n    }\n\n    function checkMustacheWithoutNamedArgs(node, pathOriginal, explicitThis, local) {\n      // {{foo.bar}} - multi-part path (not this./@ prefix), no named args\n      if (!explicitThis && pathOriginal.includes('.')) {\n        if (config.noImplicitThis && !local) {\n          reportMustache(node, pathOriginal);\n        }\n        return;\n      }\n\n      // Explicit this.foo or @foo paths are never flagged as component invocations\n      if (explicitThis) {\n        return;\n      }\n\n      if (config.allow.includes(pathOriginal)) {\n        return;\n      }\n\n      if (config.disallow.includes(pathOriginal) && !local) {\n        reportMustache(node, pathOriginal);\n        return;\n      }\n\n      if (BUILT_INS.has(pathOriginal)) {\n        return;\n      }\n\n      // {{foo-bar}} or {{nested/component}}\n      if (pathOriginal.includes('-') || pathOriginal.includes('/')) {\n        reportMustache(node, pathOriginal);\n        return;\n      }\n\n      // {{foo}} - plain single-word name, flag when noImplicitThis is enabled\n      if (config.noImplicitThis && !local) {\n        reportMustache(node, pathOriginal);\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        // <Foo @bar={{baz}} /> — mustache as an attribute value; not a component invocation\n        if (insideAttrNode) {\n          return;\n        }\n\n        if (!node.path || node.path.type !== 'GlimmerPathExpression') {\n          return;\n        }\n\n        const pathOriginal = node.path.original;\n\n        // Special case: link-to is always reported regardless of params\n        if (pathOriginal === 'link-to') {\n          reportMustache(node, pathOriginal);\n          return;\n        }\n\n        // Skip if has positional params (angle bracket syntax doesn't support positional params)\n        if (node.params && node.params.length > 0) {\n          return;\n        }\n\n        if (ALWAYS_CURLY.has(pathOriginal)) {\n          return;\n        }\n\n        const explicitThis = isExplicitThisPath(pathOriginal);\n        const firstPart = pathOriginal.split('.')[0];\n        const local = isLocalVar(firstPart) || isJsScopeBinding(node);\n\n        const hasNamedArguments = node.hash && node.hash.pairs && node.hash.pairs.length > 0;\n\n        if (hasNamedArguments) {\n          checkMustacheWithNamedArgs(node, pathOriginal, explicitThis, local);\n        } else {\n          checkMustacheWithoutNamedArgs(node, pathOriginal, explicitThis, local);\n        }\n      },\n\n      GlimmerBlockStatement(node) {\n        // Always push block params so nested mustaches can check scope.\n        blockParamStack.push(node.program?.blockParams ?? []);\n\n        if (node.inverse) {\n          // {{#foo}}bar{{else}}baz{{/foo}}\n          return;\n        }\n\n        if (!node.path || node.path.type !== 'GlimmerPathExpression') {\n          return;\n        }\n\n        const pathOriginal = node.path.original;\n\n        // Special case: link-to is always reported regardless of params\n        if (pathOriginal === 'link-to') {\n          const angleBracketName = transformTagName(pathOriginal);\n          context.report({\n            node,\n            message: `You are using the component {{#${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \\`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\\`.`,\n          });\n          return;\n        }\n\n        // Skip if has positional params\n        if (node.params && node.params.length > 0) {\n          return;\n        }\n\n        if (config.allow.includes(pathOriginal)) {\n          return;\n        }\n\n        const firstPart = pathOriginal.split('.')[0];\n        const local = isLocalVar(firstPart) || isJsScopeBinding(node);\n\n        // Explicit JS scope binding: the user imported this exact identifier.\n        // Converting {{#fooBar}}...{{/fooBar}} to <FooBar>...</FooBar> would\n        // reference a different (unbound) name. Skip the report entirely.\n        if (local && !isLocalVar(firstPart)) {\n          return;\n        }\n\n        const angleBracketName = transformTagName(pathOriginal, local);\n        context.report({\n          node,\n          message: `You are using the component {{#${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \\`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\\`.`,\n          fix: buildBlockFix(node, angleBracketName),\n        });\n      },\n\n      'GlimmerBlockStatement:exit'() {\n        blockParamStack.pop();\n      },\n\n      GlimmerElementNode(node) {\n        blockParamStack.push(node.blockParams ?? []);\n      },\n\n      'GlimmerElementNode:exit'() {\n        blockParamStack.pop();\n      },\n\n      GlimmerAttrNode() {\n        insideAttrNode = true;\n      },\n\n      'GlimmerAttrNode:exit'() {\n        insideAttrNode = false;\n      },\n    };\n  },\n};\n/* eslint-enable eslint-plugin/prefer-placeholders */\n"
  },
  {
    "path": "lib/rules/template-no-debugger.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow {{debugger}} in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-debugger.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unexpected: 'Unexpected debugger statement in template.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-debugger.js',\n      docs: 'docs/rule/no-debugger.md',\n      tests: 'test/unit/rules/no-debugger-test.js',\n    },\n  },\n\n  create(context) {\n    function checkForDebugger(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'debugger'\n      ) {\n        context.report({\n          node,\n          messageId: 'unexpected',\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        checkForDebugger(node);\n      },\n\n      GlimmerBlockStatement(node) {\n        checkForDebugger(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-deprecated.js",
    "content": "'use strict';\n\n// ts.SymbolFlags.Alias = 2097152 (1 << 21).\n// Hardcoded to avoid adding a direct `typescript` dependency. This value has\n// been stable since TypeScript was open-sourced (~2014) but is not formally\n// guaranteed. If it ever changes, this rule will need to require the user's\n// installed TypeScript and read ts.SymbolFlags.Alias at runtime.\nconst TS_ALIAS_FLAG = 2_097_152;\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'disallow using deprecated Glimmer components, helpers, and modifiers in templates',\n      category: 'Ember Octane',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-deprecated.md',\n    },\n    schema: [],\n    messages: {\n      deprecated: '`{{name}}` is deprecated.',\n      deprecatedWithReason: '`{{name}}` is deprecated. {{reason}}',\n    },\n  },\n\n  create(context) {\n    const services = context.sourceCode.parserServices;\n    if (!services?.program) {\n      return {};\n    }\n\n    const checker = services.program.getTypeChecker();\n    const sourceCode = context.sourceCode;\n\n    // Cache component class symbol → Args object type (null = no Args) per lint run.\n    const argsTypeCache = new Map();\n\n    function getComponentArgsType(classSymbol) {\n      if (argsTypeCache.has(classSymbol)) {\n        return argsTypeCache.get(classSymbol);\n      }\n      let result = null;\n      try {\n        const declaredType = checker.getDeclaredTypeOfSymbol(classSymbol);\n        const baseTypes = checker.getBaseTypes(declaredType);\n        outer: for (const base of baseTypes) {\n          for (const arg of checker.getTypeArguments(base) ?? []) {\n            const argsSymbol = arg.getProperty('Args');\n            if (argsSymbol) {\n              result = checker.getTypeOfSymbol(argsSymbol);\n              break outer;\n            }\n          }\n        }\n      } catch {\n        result = null;\n      }\n      argsTypeCache.set(classSymbol, result);\n      return result;\n    }\n\n    function getJsDocDeprecation(symbol) {\n      let jsDocTags;\n      try {\n        jsDocTags = symbol?.getJsDocTags(checker);\n      } catch {\n        // workaround for https://github.com/microsoft/TypeScript/issues/60024\n        return undefined;\n      }\n      const tag = jsDocTags?.find((t) => t.name === 'deprecated');\n      if (!tag) {\n        return undefined;\n      }\n      const displayParts = tag.text;\n      return displayParts ? displayParts.map((p) => p.text).join('') : '';\n    }\n\n    function searchForDeprecationInAliasesChain(symbol, checkAliasedSymbol) {\n      // eslint-disable-next-line no-bitwise\n      if (!symbol || !(symbol.flags & TS_ALIAS_FLAG)) {\n        return checkAliasedSymbol ? getJsDocDeprecation(symbol) : undefined;\n      }\n      const targetSymbol = checker.getAliasedSymbol(symbol);\n      let current = symbol;\n      // eslint-disable-next-line no-bitwise\n      while (current.flags & TS_ALIAS_FLAG) {\n        const reason = getJsDocDeprecation(current);\n        if (reason !== undefined) {\n          return reason;\n        }\n        const immediateAliasedSymbol =\n          current.getDeclarations() && checker.getImmediateAliasedSymbol(current);\n        if (!immediateAliasedSymbol) {\n          break;\n        }\n        current = immediateAliasedSymbol;\n        if (checkAliasedSymbol && current === targetSymbol) {\n          return getJsDocDeprecation(current);\n        }\n      }\n      return undefined;\n    }\n\n    function checkDeprecatedIdentifier(identifierNode, scope) {\n      const ref = scope.references.find((v) => v.identifier === identifierNode);\n      const variable = ref?.resolved;\n      const def = variable?.defs[0];\n\n      if (!def || def.type !== 'ImportBinding') {\n        return;\n      }\n\n      const tsNode = services.esTreeNodeToTSNodeMap.get(def.node);\n      if (!tsNode) {\n        return;\n      }\n\n      // ImportClause and ImportSpecifier require .name for getSymbolAtLocation\n      const tsIdentifier = tsNode.name ?? tsNode;\n      const symbol = checker.getSymbolAtLocation(tsIdentifier);\n      if (!symbol) {\n        return;\n      }\n\n      const reason = searchForDeprecationInAliasesChain(symbol, true);\n      if (reason === undefined) {\n        return;\n      }\n\n      if (reason === '') {\n        context.report({\n          node: identifierNode,\n          messageId: 'deprecated',\n          data: { name: identifierNode.name },\n        });\n      } else {\n        context.report({\n          node: identifierNode,\n          messageId: 'deprecatedWithReason',\n          data: { name: identifierNode.name, reason },\n        });\n      }\n    }\n\n    return {\n      GlimmerPathExpression(node) {\n        checkDeprecatedIdentifier(node.head, sourceCode.getScope(node));\n      },\n\n      GlimmerElementNode(node) {\n        // GlimmerElementNode is in its own scope; get the outer scope\n        const scope = sourceCode.getScope(node.parent);\n        checkDeprecatedIdentifier(node.parts[0], scope);\n      },\n\n      GlimmerAttrNode(node) {\n        if (!node.name.startsWith('@')) {\n          return;\n        }\n\n        // Resolve the component import binding from the parent element\n        const elementNode = node.parent;\n        const scope = sourceCode.getScope(elementNode.parent);\n        const ref = scope.references.find((v) => v.identifier === elementNode.parts[0]);\n        const def = ref?.resolved?.defs[0];\n        if (!def || def.type !== 'ImportBinding') {\n          return;\n        }\n\n        const tsNode = services.esTreeNodeToTSNodeMap.get(def.node);\n        if (!tsNode) {\n          return;\n        }\n\n        const tsIdentifier = tsNode.name ?? tsNode;\n        const importSymbol = checker.getSymbolAtLocation(tsIdentifier);\n        if (!importSymbol) {\n          return;\n        }\n\n        // Resolve alias to the class symbol\n        // eslint-disable-next-line no-bitwise\n        const classSymbol =\n          importSymbol.flags & TS_ALIAS_FLAG\n            ? checker.getAliasedSymbol(importSymbol)\n            : importSymbol;\n\n        const argsType = getComponentArgsType(classSymbol);\n        if (!argsType) {\n          return;\n        }\n\n        const argName = node.name.slice(1); // strip leading '@'\n        const argSymbol = argsType.getProperty(argName);\n        const reason = getJsDocDeprecation(argSymbol);\n        if (reason === undefined) {\n          return;\n        }\n\n        if (reason === '') {\n          context.report({\n            node,\n            messageId: 'deprecated',\n            data: { name: node.name },\n          });\n        } else {\n          context.report({\n            node,\n            messageId: 'deprecatedWithReason',\n            data: { name: node.name, reason },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-duplicate-attributes.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow duplicate attribute names in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-duplicate-attributes.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      duplicateElement: \"Duplicate attribute '{{name}}' found in the Element.\",\n      duplicateBlock: \"Duplicate attribute '{{name}}' found in the BlockStatement.\",\n      duplicateMustache: \"Duplicate attribute '{{name}}' found in the MustacheStatement.\",\n      duplicateSubExpr: \"Duplicate attribute '{{name}}' found in the SubExpression.\",\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-duplicate-attributes.js',\n      docs: 'docs/rule/no-duplicate-attributes.md',\n      tests: 'test/unit/rules/no-duplicate-attributes-test.js',\n    },\n  },\n\n  create(context) {\n    function checkForDuplicates(node, attributes, identifier, messageId) {\n      if (!attributes || attributes.length < 2) {\n        return;\n      }\n\n      const seen = new Map();\n\n      for (const attr of attributes) {\n        const key = attr[identifier];\n        if (seen.has(key)) {\n          context.report({\n            node: attr,\n            messageId,\n            data: { name: key },\n            fix(fixer) {\n              // Remove the duplicate attribute including preceding whitespace\n              const sourceCode = context.sourceCode;\n              const text = sourceCode.getText();\n              const attrStart = attr.range[0];\n              const attrEnd = attr.range[1];\n\n              // Look for whitespace before the attribute\n              let removeStart = attrStart;\n              while (removeStart > 0 && /\\s/.test(text[removeStart - 1])) {\n                removeStart--;\n              }\n\n              return fixer.removeRange([removeStart, attrEnd]);\n            },\n          });\n        } else {\n          seen.set(key, attr);\n        }\n      }\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        checkForDuplicates(node, node.attributes, 'name', 'duplicateElement');\n      },\n\n      GlimmerBlockStatement(node) {\n        const attributes = node.hash?.pairs || [];\n        checkForDuplicates(node, attributes, 'key', 'duplicateBlock');\n      },\n\n      GlimmerMustacheStatement(node) {\n        const attributes = node.hash?.pairs || [];\n        checkForDuplicates(node, attributes, 'key', 'duplicateMustache');\n      },\n\n      GlimmerSubExpression(node) {\n        const attributes = node.hash?.pairs || [];\n        checkForDuplicates(node, attributes, 'key', 'duplicateSubExpr');\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-duplicate-id.js",
    "content": "const IGNORE_IDS = new Set(['{{unique-id}}', '{{(unique-id)}}']);\n\nfunction isControlFlowHelper(node) {\n  if (node.type === 'GlimmerBlockStatement' && node.path?.type === 'GlimmerPathExpression') {\n    return ['if', 'unless', 'each', 'each-in', 'let', 'with'].includes(node.path.original);\n  }\n  return false;\n}\n\n// Walk up the parent chain to find an ancestor element/block whose blockParams\n// include headName. Used to make block-param-derived IDs unique per invocation.\nfunction findBlockParamAncestor(node, headName) {\n  if (!headName) {\n    return null;\n  }\n  let p = node.parent;\n  while (p) {\n    if (p.type === 'GlimmerElementNode' && p.blockParams?.includes(headName)) {\n      return p;\n    }\n    if (p.type === 'GlimmerBlockStatement' && p.program?.blockParams?.includes(headName)) {\n      return p;\n    }\n    p = p.parent;\n  }\n  return null;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow duplicate id attributes',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-duplicate-id.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: { duplicate: 'ID attribute values must be unique' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-duplicate-id.js',\n      docs: 'docs/rule/no-duplicate-id.md',\n      tests: 'test/unit/rules/no-duplicate-id-test.js',\n    },\n  },\n  create(context) {\n    const sourceCode = context.sourceCode;\n    let seenIdStack = [];\n    let conditionalStack = [];\n\n    function enterTemplate() {\n      seenIdStack = [new Set()];\n      conditionalStack = [];\n    }\n\n    function isDuplicateId(id) {\n      for (const seenIds of seenIdStack) {\n        if (seenIds.has(id)) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    function addId(id) {\n      seenIdStack.at(-1).add(id);\n      if (conditionalStack.length > 0) {\n        conditionalStack.at(-1).add(id);\n      }\n    }\n\n    function enterConditional() {\n      conditionalStack.push(new Set());\n    }\n\n    function exitConditional() {\n      const idsInConditional = conditionalStack.pop();\n      if (conditionalStack.length > 0) {\n        for (const id of idsInConditional) {\n          conditionalStack.at(-1).add(id);\n        }\n      } else {\n        seenIdStack.push(idsInConditional);\n      }\n    }\n\n    function enterConditionalBranch() {\n      seenIdStack.push(new Set());\n    }\n\n    function exitConditionalBranch() {\n      seenIdStack.pop();\n    }\n\n    // For a GlimmerMustacheStatement whose path is a PathExpression, return a\n    // location-aware key so that the same expression in different component\n    // invocations (e.g. {{inputProperties.id}} in two <MyComponent> blocks) gets\n    // a distinct key, while two occurrences inside the SAME block get the same key.\n    function getMustachePathKey(valueNode) {\n      const sourceText = sourceCode.getText(valueNode);\n      if (valueNode.path?.type === 'GlimmerPathExpression') {\n        const headName = valueNode.path.original.split('.')[0];\n        const ancestor = findBlockParamAncestor(valueNode, headName);\n        if (ancestor) {\n          const loc = ancestor.loc?.start;\n          const tag = ancestor.tag ?? ancestor.path?.original ?? '';\n          return `${sourceText}${tag}${loc ? `${loc.line}:${loc.column}` : ''}`;\n        }\n      }\n      return sourceText;\n    }\n\n    function resolveIdValue(valueNode) {\n      if (!valueNode) {\n        return null;\n      }\n\n      switch (valueNode.type) {\n        case 'GlimmerTextNode': {\n          return valueNode.chars || null;\n        }\n        case 'GlimmerStringLiteral': {\n          return valueNode.value || null;\n        }\n        case 'GlimmerMustacheStatement': {\n          if (valueNode.path?.type === 'GlimmerStringLiteral') {\n            return valueNode.path.value;\n          }\n          return getMustachePathKey(valueNode);\n        }\n        case 'GlimmerConcatStatement': {\n          if (valueNode.parts) {\n            return valueNode.parts\n              .map((part) => {\n                if (part.type === 'GlimmerTextNode') {\n                  return part.chars;\n                }\n                if (\n                  part.type === 'GlimmerMustacheStatement' &&\n                  part.path?.type === 'GlimmerStringLiteral'\n                ) {\n                  return part.path.value;\n                }\n                if (part.type === 'GlimmerMustacheStatement') {\n                  return getMustachePathKey(part);\n                }\n                return sourceCode.getText(part);\n              })\n              .join('');\n          }\n          return sourceCode.getText(valueNode);\n        }\n        default: {\n          return sourceCode.getText(valueNode);\n        }\n      }\n    }\n\n    function logIfDuplicate(reportNode, id) {\n      if (!id) {\n        return;\n      }\n      if (IGNORE_IDS.has(id)) {\n        return;\n      }\n      if (isDuplicateId(id)) {\n        context.report({ node: reportNode, messageId: 'duplicate' });\n      } else {\n        addId(id);\n      }\n    }\n\n    return {\n      GlimmerTemplate() {\n        enterTemplate();\n      },\n      'GlimmerTemplate:exit'() {\n        seenIdStack = [new Set()];\n        conditionalStack = [];\n      },\n\n      GlimmerElementNode(node) {\n        // Note: no blockParams scoping here. Elements with block params (e.g.\n        // <MyComponent as |foo|>) must not isolate their static IDs — a static\n        // \"shared-id\" inside and outside such an element ARE duplicates.\n        const idAttrNames = new Set(['id', '@id', '@elementId']);\n        for (const attr of node.attributes || []) {\n          if (idAttrNames.has(attr.name)) {\n            const id = resolveIdValue(attr.value);\n            logIfDuplicate(attr, id);\n          }\n        }\n      },\n\n      // Handle hash pairs in mustache/block statements (e.g., {{input elementId=\"foo\"}})\n      GlimmerMustacheStatement(node) {\n        if (node.hash && node.hash.pairs) {\n          for (const pair of node.hash.pairs) {\n            if (['elementId', 'id'].includes(pair.key)) {\n              if (pair.value?.type === 'GlimmerStringLiteral') {\n                logIfDuplicate(pair, pair.value.value);\n              }\n            }\n          }\n        }\n      },\n\n      GlimmerBlockStatement(node) {\n        if (isControlFlowHelper(node)) {\n          enterConditional();\n        } else if (node.hash && node.hash.pairs) {\n          for (const pair of node.hash.pairs) {\n            if (['elementId', 'id'].includes(pair.key)) {\n              if (pair.value?.type === 'GlimmerStringLiteral') {\n                logIfDuplicate(pair, pair.value.value);\n              }\n            }\n          }\n        }\n      },\n\n      'GlimmerBlockStatement:exit'(node) {\n        if (isControlFlowHelper(node)) {\n          exitConditional();\n        }\n      },\n\n      GlimmerBlock(node) {\n        const parent = node.parent;\n        if (parent && isControlFlowHelper(parent)) {\n          enterConditionalBranch();\n        }\n      },\n\n      'GlimmerBlock:exit'(node) {\n        const parent = node.parent;\n        if (parent && isControlFlowHelper(parent)) {\n          exitConditionalBranch();\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-duplicate-landmark-elements.js",
    "content": "// This rule inspects aria-label / aria-labelledby before classifying a node\n// as a landmark (see getLabel + the dynamic-label skip in GlimmerElementNode),\n// so it can safely include `region` — it won't flag an unnamed <section> as\n// a landmark duplicate. Use the full spec-listed 8-role set.\nconst { ALL_LANDMARK_ROLES: LANDMARK_ROLES } = require('../utils/landmark-roles');\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow duplicate landmark elements without unique labels',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-duplicate-landmark-elements.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      duplicate:\n        'If multiple landmark elements (or elements with an equivalent role) of the same type are found on a page, they must each have a unique label.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-duplicate-landmark-elements.js',\n      docs: 'docs/rule/no-duplicate-landmark-elements.md',\n      tests: 'test/unit/rules/no-duplicate-landmark-elements-test.js',\n    },\n  },\n\n  create(context) {\n    // Map HTML5 landmark elements to their implicit ARIA roles\n    const ELEMENT_TO_ROLE = {\n      header: 'banner',\n      footer: 'contentinfo',\n      main: 'main',\n      nav: 'navigation',\n      aside: 'complementary',\n      form: 'form',\n    };\n\n    // Sectioning elements that strip banner/contentinfo roles from header/footer\n    const SECTIONING_ELEMENTS = new Set(['article', 'aside', 'main', 'nav', 'section']);\n    const elementStack = [];\n\n    // Stack-based scoping for landmarks to handle conditional branches.\n    // Each scope is a Map<role, [{node, tag}]>.\n    // When entering a GlimmerBlock that is a branch of an if/unless,\n    // we push a copy of the current scope so that each branch starts\n    // from the same baseline and landmarks from one branch don't\n    // leak into another.\n    const landmarksStack = [new Map()];\n\n    function currentLandmarks() {\n      return landmarksStack.at(-1);\n    }\n\n    function cloneLandmarks(landmarks) {\n      const clone = new Map();\n      for (const [role, entries] of landmarks) {\n        clone.set(role, [...entries]);\n      }\n      return clone;\n    }\n\n    function isInsideSectioningElement() {\n      return elementStack.some((tag) => SECTIONING_ELEMENTS.has(tag));\n    }\n\n    function checkDuplicates(landmarks) {\n      for (const [, entries] of landmarks) {\n        if (entries.length > 1) {\n          const labeled = [];\n          const unlabeled = [];\n\n          for (const entry of entries) {\n            const label = getLabel(entry.node);\n            if (label) {\n              labeled.push({ node: entry.node, tag: entry.tag, label });\n            } else {\n              unlabeled.push({ node: entry.node, tag: entry.tag });\n            }\n          }\n\n          // When multiple landmarks of same type exist, unlabeled ones are violations\n          if (unlabeled.length > 0) {\n            if (unlabeled.length === entries.length) {\n              // All unlabeled — report all but the first\n              for (let i = 1; i < unlabeled.length; i++) {\n                context.report({\n                  node: unlabeled[i].node,\n                  messageId: 'duplicate',\n                });\n              }\n            } else {\n              // Some are labeled, some aren't — report the unlabeled ones\n              for (const entry of unlabeled) {\n                context.report({\n                  node: entry.node,\n                  messageId: 'duplicate',\n                });\n              }\n            }\n          }\n\n          // Report same-label duplicates among labeled landmarks\n          const labelGroups = new Map();\n          for (const entry of labeled) {\n            if (!labelGroups.has(entry.label)) {\n              labelGroups.set(entry.label, []);\n            }\n            labelGroups.get(entry.label).push(entry);\n          }\n          for (const [, groupEntries] of labelGroups) {\n            if (groupEntries.length > 1) {\n              for (let i = 1; i < groupEntries.length; i++) {\n                context.report({\n                  node: groupEntries[i].node,\n                  messageId: 'duplicate',\n                });\n              }\n            }\n          }\n        }\n      }\n    }\n\n    // Track elements that create new landmark scopes (dialog, popover)\n    const newScopeElements = [];\n\n    return {\n      GlimmerElementNode(node) {\n        elementStack.push(node.tag);\n\n        // <dialog> and elements with popover attribute create isolated landmark scopes\n        if (isNewScopeElement(node)) {\n          landmarksStack.push(new Map());\n          newScopeElements.push(node);\n        }\n\n        // Dynamic role values — skip (can't determine role statically)\n        const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n        if (roleAttr && roleAttr.value?.type !== 'GlimmerTextNode') {\n          return;\n        }\n\n        const landmarkRole = getLandmarkRole(\n          node,\n          LANDMARK_ROLES,\n          ELEMENT_TO_ROLE,\n          isInsideSectioningElement()\n        );\n        if (landmarkRole) {\n          // Dynamic aria-label / aria-labelledby — can't statically determine whether\n          // this landmark duplicates a sibling, so skip registering it entirely.\n          const labelAttr =\n            node.attributes?.find((attr) => attr.name === 'aria-label') ||\n            node.attributes?.find((attr) => attr.name === 'aria-labelledby');\n          if (labelAttr && labelAttr.value?.type !== 'GlimmerTextNode') {\n            return;\n          }\n\n          const landmarks = currentLandmarks();\n          if (!landmarks.has(landmarkRole)) {\n            landmarks.set(landmarkRole, []);\n          }\n          landmarks.get(landmarkRole).push({ node, tag: node.tag });\n        }\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        elementStack.pop();\n        if (newScopeElements.at(-1) === node) {\n          newScopeElements.pop();\n          landmarksStack.pop();\n        }\n      },\n\n      // Every Block gets its own scope (matching the original's Block visitor).\n      // This handles each, let, with, etc. — not just if/unless.\n      GlimmerBlock() {\n        landmarksStack.push(cloneLandmarks(currentLandmarks()));\n      },\n\n      'GlimmerBlock:exit'() {\n        landmarksStack.pop();\n      },\n\n      'Program:exit'() {\n        checkDuplicates(currentLandmarks());\n      },\n    };\n  },\n};\n\nfunction getLabel(node) {\n  // Check aria-label\n  const ariaLabel = node.attributes?.find((attr) => attr.name === 'aria-label');\n  if (ariaLabel) {\n    if (ariaLabel.value?.type === 'GlimmerTextNode') {\n      return ariaLabel.value.chars.trim();\n    }\n    // Dynamic aria-label — treat as a unique label (can't statically determine duplicates)\n    return `__dynamic:${ariaLabel.range?.[0] || Math.random()}`;\n  }\n\n  // Check aria-labelledby - extract the ID value\n  const ariaLabelledby = node.attributes?.find((attr) => attr.name === 'aria-labelledby');\n  if (ariaLabelledby) {\n    if (ariaLabelledby.value?.type === 'GlimmerTextNode') {\n      return `__labelledby:${ariaLabelledby.value.chars.trim()}`;\n    }\n    return `__dynamic:${ariaLabelledby.range?.[0] || Math.random()}`;\n  }\n\n  return null;\n}\n\nfunction getRoleValue(node) {\n  const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n  if (roleAttr && roleAttr.value?.type === 'GlimmerTextNode') {\n    return roleAttr.value.chars.trim();\n  }\n  return null;\n}\n\nfunction isNewScopeElement(node) {\n  return node.tag === 'dialog' || node.attributes?.some((attr) => attr.name === 'popover');\n}\n\nfunction getLandmarkRole(node, LANDMARK_ROLES, ELEMENT_TO_ROLE, insideSectioning) {\n  const role = getRoleValue(node);\n  if (role && LANDMARK_ROLES.has(role)) {\n    return role;\n  }\n  const implicitRole = ELEMENT_TO_ROLE[node.tag];\n  if (implicitRole) {\n    // header and footer lose their landmark role when inside sectioning elements\n    if (insideSectioning && (node.tag === 'header' || node.tag === 'footer')) {\n      return null;\n    }\n    return implicitRole;\n  }\n  return null;\n}\n"
  },
  {
    "path": "lib/rules/template-no-dynamic-subexpression-invocations.js",
    "content": "function isInAttrPosition(node) {\n  let p = node.parent;\n  while (p) {\n    if (p.type === 'GlimmerAttrNode') {\n      return true;\n    }\n    if (p.type === 'GlimmerConcatStatement') {\n      p = p.parent;\n      continue;\n    }\n    if (\n      p.type === 'GlimmerElementNode' ||\n      p.type === 'GlimmerTemplate' ||\n      p.type === 'GlimmerBlockStatement' ||\n      p.type === 'GlimmerBlock'\n    ) {\n      return false;\n    }\n    p = p.parent;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow dynamic subexpression invocations',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-dynamic-subexpression-invocations.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noDynamicSubexpressionInvocations:\n        'Do not use dynamic helper invocations. Use explicit helper names instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-dynamic-subexpression-invocations.js',\n      docs: 'docs/rule/no-dynamic-subexpression-invocations.md',\n      tests: 'test/unit/rules/no-dynamic-subexpression-invocations-test.js',\n    },\n  },\n\n  create(context) {\n    const localScopes = [];\n\n    function pushLocals(params) {\n      localScopes.push(new Set(params || []));\n    }\n\n    function popLocals() {\n      localScopes.pop();\n    }\n\n    function isLocal(name) {\n      for (const scope of localScopes) {\n        if (scope.has(name)) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    function isDynamicPath(path) {\n      if (!path || path.type !== 'GlimmerPathExpression') {\n        return false;\n      }\n      if (path.head?.type === 'AtHead') {\n        return true;\n      }\n      if (path.head?.type === 'ThisHead') {\n        return true;\n      }\n      if (path.original && path.original.includes('.')) {\n        return true;\n      }\n      if (path.original && isLocal(path.original)) {\n        return true;\n      }\n      return false;\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        if (node.program && node.program.blockParams) {\n          pushLocals(node.program.blockParams);\n        }\n      },\n      'GlimmerBlockStatement:exit'(node) {\n        if (node.program && node.program.blockParams) {\n          popLocals();\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          pushLocals(node.blockParams);\n        }\n      },\n      'GlimmerElementNode:exit'(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          popLocals();\n        }\n      },\n\n      GlimmerSubExpression(node) {\n        if (node.path && node.path.type === 'GlimmerPathExpression' && isDynamicPath(node.path)) {\n          context.report({\n            node,\n            messageId: 'noDynamicSubexpressionInvocations',\n          });\n        }\n      },\n\n      GlimmerElementModifierStatement(node) {\n        if (node.path && node.path.type === 'GlimmerPathExpression' && isDynamicPath(node.path)) {\n          context.report({\n            node,\n            messageId: 'noDynamicSubexpressionInvocations',\n          });\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (node.path && node.path.type === 'GlimmerPathExpression') {\n          const inAttr = isInAttrPosition(node);\n          const hasArgs =\n            (node.params && node.params.length > 0) ||\n            (node.hash && node.hash.pairs && node.hash.pairs.length > 0);\n\n          if (inAttr && isDynamicPath(node.path) && hasArgs) {\n            // In attribute context, flag dynamic paths with arguments\n            context.report({\n              node,\n              messageId: 'noDynamicSubexpressionInvocations',\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-element-event-actions.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow element event actions (use {{on}} modifier instead)',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-element-event-actions.md',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          requireActionHelper: { type: 'boolean' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      noElementEventActions: 'Do not use element event actions. Use the `on` modifier instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-element-event-actions.js',\n      docs: 'docs/rule/no-element-event-actions.md',\n      tests: 'test/unit/rules/no-element-event-actions-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const requireActionHelper = options.requireActionHelper || false;\n\n    return {\n      GlimmerElementNode(node) {\n        if (!node.attributes) {\n          return;\n        }\n\n        for (const attr of node.attributes) {\n          if (attr.type !== 'GlimmerAttrNode' || !attr.name) {\n            continue;\n          }\n          const name = attr.name.toLowerCase();\n          if (!name.startsWith('on')) {\n            continue;\n          }\n          // Skip non-event attributes like \"once\", \"open\", etc.\n          if (name.length <= 2) {\n            continue;\n          }\n\n          // If requireActionHelper is true, only flag when the value uses {{action ...}}\n          if (requireActionHelper) {\n            if (\n              attr.value?.type === 'GlimmerMustacheStatement' &&\n              attr.value.path?.original === 'action'\n            ) {\n              context.report({ node: attr, messageId: 'noElementEventActions' });\n            }\n          } else {\n            // Flag any mustache value on event attributes\n            if (\n              attr.value?.type === 'GlimmerMustacheStatement' ||\n              attr.value?.type === 'GlimmerConcatStatement'\n            ) {\n              context.report({ node: attr, messageId: 'noElementEventActions' });\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-empty-headings.js",
    "content": "const { getStaticAttrValue } = require('../utils/static-attr-value');\nconst { isNativeElement } = require('../utils/is-native-element');\n\nconst HEADINGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);\n\n// Aligned with the WAI-ARIA 1.2 [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden)\n// value table (`true | false | undefined (default)`): treat only an explicit\n// \"true\" (ASCII case-insensitive, whitespace-trimmed) as hiding the element.\n// Valueless `<h1 aria-hidden>`, empty-string `aria-hidden=\"\"`, and\n// `aria-hidden=\"false\"` all resolve to the default `undefined` / explicit\n// false — so the empty-content check still applies. All shape-unwrapping\n// (mustache/concat) goes through the shared `getStaticAttrValue` helper.\nfunction isAriaHiddenTruthy(attr) {\n  if (!attr) {\n    return false;\n  }\n  const resolved = getStaticAttrValue(attr.value);\n  if (resolved === undefined) {\n    // Dynamic — can't prove truthy.\n    return false;\n  }\n  return resolved.trim().toLowerCase() === 'true';\n}\n\nfunction isHidden(node) {\n  if (!node.attributes) {\n    return false;\n  }\n  if (node.attributes.some((a) => a.name === 'hidden')) {\n    return true;\n  }\n  return isAriaHiddenTruthy(node.attributes.find((a) => a.name === 'aria-hidden'));\n}\n\nfunction isTextEmpty(text) {\n  // Treat &nbsp; (U+00A0) and regular whitespace as empty\n  return text.replaceAll(/\\s/g, '').replaceAll('&nbsp;', '').length === 0;\n}\n\nfunction hasAccessibleContent(node, sourceCode) {\n  if (!node.children || node.children.length === 0) {\n    return false;\n  }\n\n  for (const child of node.children) {\n    // Text nodes — only counts if it has real visible characters\n    if (child.type === 'GlimmerTextNode') {\n      if (!isTextEmpty(child.chars)) {\n        return true;\n      }\n      continue;\n    }\n\n    // Mustache/block statements are dynamic content\n    if (child.type === 'GlimmerMustacheStatement' || child.type === 'GlimmerBlockStatement') {\n      return true;\n    }\n\n    // Element nodes\n    if (child.type === 'GlimmerElementNode') {\n      // Skip hidden elements entirely\n      if (isHidden(child)) {\n        continue;\n      }\n\n      // Component invocations (including custom elements and scope-bound\n      // identifiers) are opaque — we can't see inside, so assume content.\n      if (!isNativeElement(child, sourceCode)) {\n        return true;\n      }\n\n      // Recurse into native HTML/SVG/MathML elements.\n      if (hasAccessibleContent(child, sourceCode)) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nfunction isHeadingElement(node) {\n  if (HEADINGS.has(node.tag)) {\n    return true;\n  }\n  // Also detect <div role=\"heading\" ...>\n  const roleAttr = node.attributes?.find((a) => a.name === 'role');\n  if (roleAttr?.value?.type === 'GlimmerTextNode' && roleAttr.value.chars === 'heading') {\n    return true;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow empty heading elements',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-empty-headings.md',\n    },\n    schema: [],\n    messages: {\n      emptyHeading:\n        'Headings must contain accessible text content (or helper/component that provides text).',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-empty-headings.js',\n      docs: 'docs/rule/no-empty-headings.md',\n      tests: 'test/unit/rules/no-empty-headings-test.js',\n    },\n  },\n  create(context) {\n    const sourceCode = context.sourceCode;\n    return {\n      GlimmerElementNode(node) {\n        if (isHeadingElement(node)) {\n          // Skip if the heading itself is hidden\n          if (isHidden(node)) {\n            return;\n          }\n\n          if (!hasAccessibleContent(node, sourceCode)) {\n            context.report({ node, messageId: 'emptyHeading' });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-extra-mut-helper-argument.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow passing more than one argument to the mut helper',\n      category: 'Possible Errors',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-extra-mut-helper-argument.md',\n      templateMode: 'loose',\n    },\n    fixable: null,\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-extra-mut-helper-argument.js',\n      docs: 'docs/rule/no-extra-mut-helper-argument.md',\n      tests: 'test/unit/rules/no-extra-mut-helper-argument-test.js',\n    },\n  },\n\n  create(context) {\n    const ERROR_MESSAGE =\n      'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.';\n\n    return {\n      GlimmerSubExpression(node) {\n        if (\n          node.path &&\n          node.path.type === 'GlimmerPathExpression' &&\n          node.path.original === 'mut'\n        ) {\n          if (node.params && node.params.length > 1) {\n            context.report({\n              node,\n              message: ERROR_MESSAGE,\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-forbidden-elements.js",
    "content": "const DEFAULT_FORBIDDEN = ['meta', 'style', 'html', 'script'];\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow specific HTML elements',\n      category: 'Best Practices',\n      recommendedGjs: false,\n      recommendedGts: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-forbidden-elements.md',\n      templateMode: 'both',\n    },\n    schema: [\n      {\n        oneOf: [\n          { type: 'array', items: { type: 'string' } },\n          { type: 'boolean' },\n          {\n            type: 'object',\n            properties: {\n              forbidden: { type: 'array', items: { type: 'string' } },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: { forbidden: 'Use of forbidden element <{{element}}>' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-forbidden-elements.js',\n      docs: 'docs/rule/no-forbidden-elements.md',\n      tests: 'test/unit/rules/no-forbidden-elements-test.js',\n    },\n  },\n  create(context) {\n    const rawConfig = context.options[0];\n    let forbiddenList;\n\n    if (rawConfig === true || rawConfig === undefined) {\n      forbiddenList = DEFAULT_FORBIDDEN;\n    } else if (Array.isArray(rawConfig)) {\n      forbiddenList = rawConfig;\n    } else if (rawConfig && typeof rawConfig === 'object') {\n      forbiddenList = rawConfig.forbidden ?? DEFAULT_FORBIDDEN;\n    } else {\n      forbiddenList = [];\n    }\n\n    const forbidden = new Set(forbiddenList);\n\n    // Track element stack for <meta> in <head> exception\n    const elementStack = [];\n\n    return {\n      GlimmerElementNode(node) {\n        elementStack.push(node.tag);\n\n        if (!forbidden.has(node.tag)) {\n          return;\n        }\n\n        // Exception: <meta> inside <head> is allowed\n        if (node.tag === 'meta' && elementStack.includes('head')) {\n          return;\n        }\n\n        context.report({ node, messageId: 'forbidden', data: { element: node.tag } });\n      },\n      'GlimmerElementNode:exit'() {\n        elementStack.pop();\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-heading-inside-button.js",
    "content": "const HEADING_ELEMENTS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);\n\nfunction hasButtonParent(node) {\n  let parent = node.parent;\n  while (parent) {\n    if (parent.type === 'GlimmerElementNode') {\n      // Check if it's a button element\n      if (parent.tag === 'button') {\n        return true;\n      }\n      // Check if it has role=\"button\"\n      const roleAttr = parent.attributes?.find((a) => a.name === 'role');\n      if (roleAttr?.value?.type === 'GlimmerTextNode' && roleAttr.value.chars === 'button') {\n        return true;\n      }\n    }\n    parent = parent.parent;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow heading elements inside button elements',\n      category: 'Accessibility',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-heading-inside-button.md',\n    },\n    schema: [],\n    messages: {\n      noHeading: 'Buttons should not contain heading elements',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-heading-inside-button.js',\n      docs: 'docs/rule/no-heading-inside-button.md',\n      tests: 'test/unit/rules/no-heading-inside-button-test.js',\n    },\n  },\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (HEADING_ELEMENTS.has(node.tag) && hasButtonParent(node)) {\n          context.report({ node, messageId: 'noHeading' });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-html-comments.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow HTML comments in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-html-comments.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noHtmlComments:\n        'HTML comments should not be used in templates. Use Handlebars comments instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-html-comments.js',\n      docs: 'docs/rule/no-html-comments.md',\n      tests: 'test/unit/rules/no-html-comments-test.js',\n    },\n  },\n\n  create(context) {\n    const glimmerTemplateRanges = [];\n\n    return {\n      GlimmerTemplate(node) {\n        glimmerTemplateRanges.push(node.range);\n      },\n\n      'Program:exit'() {\n        const sourceCode = context.sourceCode;\n        const fullText = sourceCode.getText();\n\n        for (const comment of sourceCode.getAllComments()) {\n          if (comment.type !== 'Block') {\n            continue;\n          }\n\n          // getAllComments() returns both HTML (<!-- -->) and Handlebars ({{! }}, {{!-- --}})\n          // comments as Block type — only flag actual HTML comments.\n          if (!fullText.startsWith('<!--', comment.range[0])) {\n            continue;\n          }\n\n          const isInTemplate = glimmerTemplateRanges.some(\n            ([start, end]) => comment.range[0] >= start && comment.range[1] <= end\n          );\n\n          if (isInTemplate) {\n            context.report({\n              loc: comment.loc,\n              messageId: 'noHtmlComments',\n              fix(fixer) {\n                return fixer.replaceTextRange(comment.range, `{{!${comment.value}}}`);\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-implicit-this.js",
    "content": "// Built-in helpers and keywords\nconst BUILT_INS = new Set([\n  'yield',\n  'outlet',\n  'has-block',\n  'has-block-params',\n  'hasBlock',\n  'hasBlockParams',\n  'if',\n  'unless',\n  'each',\n  'let',\n  'with',\n  'each-in',\n  'concat',\n  'get',\n  'array',\n  'hash',\n  'log',\n  'debugger',\n  'component',\n  'helper',\n  'modifier',\n  'mount',\n  'input',\n  'textarea',\n  'query-params',\n  'unique-id',\n  // arg-less components/helpers from the default ember-cli blueprint\n  'welcome-page',\n  'rootURL',\n]);\n\n// Node types that have a `path` property pointing to a callee PathExpression\nconst CALLEE_PARENT_TYPES = new Set([\n  'GlimmerMustacheStatement',\n  'GlimmerSubExpression',\n  'GlimmerBlockStatement',\n  'GlimmerElementModifierStatement',\n]);\n\n// Callees are always valid for SubExpression/Block/Modifier; for Mustache,\n// only when the mustache has args (bare {{foo}} is still ambiguous).\nfunction isCalleePosition(node) {\n  const parent = node.parent;\n  if (!parent || !CALLEE_PARENT_TYPES.has(parent.type) || parent.path !== node) {\n    return false;\n  }\n  if (parent.type !== 'GlimmerMustacheStatement') {\n    return true;\n  }\n  const hasParams = parent.params && parent.params.length > 0;\n  const hasHash = parent.hash && parent.hash.pairs && parent.hash.pairs.length > 0;\n  return hasParams || hasHash;\n}\n\n// Returns true if the path root resolves to a JS binding (import, const,\n// param, etc.). Walks scope.variables by name so it catches Glimmer built-in\n// names (e.g. log, outlet) that don't surface in scope.references.\nfunction isJsScopeVariable(node, sourceCode) {\n  if (!sourceCode || !node.original) {\n    return false;\n  }\n  const name = node.original.split('.')[0];\n  try {\n    let scope = sourceCode.getScope(node);\n    while (scope) {\n      if (scope.variables.some((v) => v.name === name)) {\n        return true;\n      }\n      scope = scope.upper;\n    }\n  } catch {\n    // sourceCode.getScope may not be available in .hbs-only mode; ignore.\n  }\n  return false;\n}\n\n// Walks ancestors collecting block params from GlimmerBlockStatement nodes.\nfunction isLocalBlockParam(node, pathRoot) {\n  let current = node.parent;\n  while (current) {\n    // GlimmerBlockStatement nodes carry block params in program.blockParams\n    if (current.type === 'GlimmerBlockStatement') {\n      const blockParams = current.program?.blockParams || current.blockParams || [];\n      if (blockParams.includes(pathRoot)) {\n        return true;\n      }\n    }\n    current = current.parent;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require explicit `this` in property access',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-implicit-this.md',\n      templateMode: 'loose',\n    },\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allow: {\n            type: 'array',\n            items: { anyOf: [{ type: 'string' }, { instanceof: 'RegExp' }] },\n            uniqueItems: false,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      noImplicitThis:\n        'Ambiguous path \"{{path}}\" is not allowed. Use \"@{{path}}\" if it is a named argument or \"this.{{path}}\" if it is a property on the component.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-implicit-this.js',\n      docs: 'docs/rule/no-implicit-this.md',\n      tests: 'test/unit/rules/no-implicit-this-test.js',\n    },\n  },\n\n  create(context) {\n    const allowList = context.options[0]?.allow || [];\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerPathExpression(node) {\n        const path = node.original;\n\n        // Skip if path starts with @ (named arg) or this. (explicit)\n        if (path.startsWith('@') || path.startsWith('this.') || path === 'this') {\n          return;\n        }\n\n        // Skip built-in helpers and keywords\n        if (BUILT_INS.has(path)) {\n          return;\n        }\n\n        // Skip paths matching the allow list (exact string or regex)\n        if (allowList.some((item) => (item instanceof RegExp ? item.test(path) : item === path))) {\n          return;\n        }\n\n        // Skip if it looks like a component (PascalCase)\n        const firstPart = path.split('.')[0];\n        if (firstPart[0] === firstPart[0].toUpperCase()) {\n          return;\n        }\n\n        // Skip callees of call-like expressions (SubExpression, BlockStatement,\n        // ElementModifierStatement always; MustacheStatement only with args)\n        if (isCalleePosition(node)) {\n          return;\n        }\n\n        // Skip paths whose root is a JS scope binding (import/const/param) —\n        // this is how GJS/GTS references external helpers, components, values.\n        if (isJsScopeVariable(node, sourceCode)) {\n          return;\n        }\n\n        // Skip paths whose root is an in-scope block param\n        if (isLocalBlockParam(node, firstPart)) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId: 'noImplicitThis',\n          data: { path },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-index-component-invocation.js",
    "content": "/* eslint-disable complexity, eslint-plugin/prefer-placeholders, unicorn/explicit-length-check */\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow index component invocations',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-index-component-invocation.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-index-component-invocation.js',\n      docs: 'docs/rule/no-index-component-invocation.md',\n      tests: 'test/unit/rules/no-index-component-invocation-test.js',\n    },\n  },\n\n  create(context) {\n    function lintIndexUsage(node) {\n      // Handle angle bracket components: <Foo::Index />\n      if (node.type === 'GlimmerElementNode') {\n        if (node.tag && node.tag.endsWith('::Index')) {\n          const invocation = `<${node.tag}`;\n          const replacement = `<${node.tag.replace('::Index', '')}`;\n\n          context.report({\n            node,\n            message: `Replace \\`${invocation} ...\\` to \\`${replacement} ...\\``,\n          });\n        }\n        return;\n      }\n\n      // Handle mustache and block statements: {{foo/index}} or {{#foo/index}}\n      if (node.type === 'GlimmerMustacheStatement' || node.type === 'GlimmerBlockStatement') {\n        if (\n          node.path &&\n          node.path.type === 'GlimmerPathExpression' &&\n          node.path.original &&\n          node.path.original.endsWith('/index')\n        ) {\n          const invocationPrefix = node.type === 'GlimmerBlockStatement' ? '{{#' : '{{';\n          const invocation = `${invocationPrefix}${node.path.original}`;\n          const replacement = `${invocationPrefix}${node.path.original.replace('/index', '')}`;\n\n          context.report({\n            node: node.path,\n            message: `Replace \\`${invocation} ...\\` to \\`${replacement} ...\\``,\n          });\n          return;\n        }\n      }\n\n      // Handle component helper: {{component \"foo/index\"}} or (component \"foo/index\")\n      if (\n        node.type === 'GlimmerMustacheStatement' ||\n        node.type === 'GlimmerBlockStatement' ||\n        node.type === 'GlimmerSubExpression'\n      ) {\n        const prefix =\n          node.type === 'GlimmerMustacheStatement'\n            ? '{{'\n            : node.type === 'GlimmerBlockStatement'\n              ? '{{#'\n              : '(';\n\n        if (\n          node.path &&\n          node.path.type === 'GlimmerPathExpression' &&\n          node.path.original === 'component' &&\n          node.params &&\n          node.params.length > 0 &&\n          node.params[0].type === 'GlimmerStringLiteral'\n        ) {\n          const componentName = node.params[0].value;\n\n          if (componentName.endsWith('/index')) {\n            const invocation = `${prefix}component \"${componentName}\"`;\n            const replacement = `${prefix}component \"${componentName.replace('/index', '')}\"`;\n\n            context.report({\n              node: node.params[0],\n              message: `Replace \\`${invocation} ...\\` to \\`${replacement} ...\\``,\n            });\n          }\n        }\n      }\n    }\n\n    return {\n      GlimmerElementNode: lintIndexUsage,\n      GlimmerMustacheStatement: lintIndexUsage,\n      GlimmerBlockStatement: lintIndexUsage,\n      GlimmerSubExpression: lintIndexUsage,\n    };\n  },\n};\n/* eslint-enable complexity, eslint-plugin/prefer-placeholders, unicorn/explicit-length-check */\n"
  },
  {
    "path": "lib/rules/template-no-inline-event-handlers.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow DOM event handler attributes',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-inline-event-handlers.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unexpected:\n        'Do not use inline event handlers like \"{{name}}\". Use the (on) modifier instead.',\n    },\n  },\n\n  create(context) {\n    const EVENT_HANDLER_ATTRS = new Set([\n      'onclick',\n      'ondblclick',\n      'onmousedown',\n      'onmouseup',\n      'onmousemove',\n      'onmouseout',\n      'onmouseover',\n      'onkeydown',\n      'onkeyup',\n      'onkeypress',\n      'onchange',\n      'oninput',\n      'onsubmit',\n      'onfocus',\n      'onblur',\n      'onload',\n      'onerror',\n      'onscroll',\n    ]);\n\n    return {\n      GlimmerElementNode(node) {\n        if (node.attributes) {\n          for (const attr of node.attributes) {\n            if (attr.name && EVENT_HANDLER_ATTRS.has(attr.name.toLowerCase())) {\n              context.report({\n                node: attr,\n                messageId: 'unexpected',\n                data: { name: attr.name },\n              });\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-inline-linkto.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow inline form of LinkTo component',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-inline-linkto.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noInlineLinkTo: 'Use block form of LinkTo component instead of inline form.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/inline-link-to.js',\n      docs: 'docs/rule/inline-link-to.md',\n      tests: 'test/unit/rules/inline-link-to-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // In HBS, `<LinkTo>` always refers to Ember's router link component.\n    // In GJS/GTS, `<LinkTo>` must be explicitly imported from '@ember/routing'\n    // (and may be renamed, e.g. `import { LinkTo as Link } from '@ember/routing'`).\n    // Limitation: namespace imports (`import * as routing from '@ember/routing'`\n    // → `<routing.LinkTo />`) are not tracked — dotted tag paths would need a\n    // separate match and are not a realistic usage pattern for this component.\n    const importedLinkComponents = new Set();\n\n    function isLinkToComponent(node) {\n      if (isStrictMode) {\n        return importedLinkComponents.has(node.tag);\n      }\n      return node.tag === 'LinkTo';\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (!isStrictMode) {\n          return;\n        }\n        if (node.source.value !== '@ember/routing') {\n          return;\n        }\n        for (const specifier of node.specifiers) {\n          if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'LinkTo') {\n            importedLinkComponents.add(specifier.local.name);\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (!isLinkToComponent(node)) {\n          return;\n        }\n        if (node.children && node.children.length === 0) {\n          context.report({\n            node,\n            messageId: 'noInlineLinkTo',\n          });\n        }\n      },\n\n      // {{link-to 'text' 'route'}} inline curly form — HBS-only.\n      // The `link-to` kebab path is not a valid JS identifier, so it cannot\n      // be a user binding in strict mode; the strict-mode compiler would\n      // already reject the source. Skip the curly handler in strict mode to\n      // avoid emitting a fix that produces also-broken `{{#link-to ...}}`.\n      GlimmerMustacheStatement(node) {\n        if (isStrictMode) {\n          return;\n        }\n        if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'link-to') {\n          const sourceCode = context.sourceCode;\n          const titleNode = node.params[0];\n          const isFixable =\n            titleNode &&\n            (titleNode.type === 'GlimmerStringLiteral' ||\n              titleNode.type === 'GlimmerSubExpression');\n\n          context.report({\n            node,\n            messageId: 'noInlineLinkTo',\n            fix: isFixable\n              ? (fixer) => {\n                  // Build the block body content from the first param (the link text)\n                  let titleContent;\n                  if (titleNode.type === 'GlimmerStringLiteral') {\n                    titleContent = titleNode.value;\n                  } else {\n                    // SubExpression: (helper ...) → {{helper ...}}\n                    const src = sourceCode.getText(titleNode);\n                    titleContent = `{{${src.slice(1, -1)}}}`;\n                  }\n\n                  // Remaining positional params become the block's params\n                  const remainingParams = node.params\n                    .slice(1)\n                    .map((p) => sourceCode.getText(p))\n                    .join(' ');\n\n                  // Hash pairs (named args) are preserved as-is\n                  const hashPairs =\n                    node.hash?.pairs?.length > 0\n                      ? ` ${node.hash.pairs.map((p) => sourceCode.getText(p)).join(' ')}`\n                      : '';\n\n                  const paramsSep = remainingParams ? ' ' : '';\n                  const newText = `{{#link-to${paramsSep}${remainingParams}${hashPairs}}}${titleContent}{{/link-to}}`;\n\n                  return fixer.replaceText(node, newText);\n                }\n              : null,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-inline-styles.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow inline styles',\n      category: 'Best Practices',\n      recommendedGjs: false,\n      recommendedGts: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-inline-styles.md',\n    },\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowDynamicStyles: { type: 'boolean' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: { noInlineStyles: 'Inline styles are not allowed' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-inline-styles.js',\n      docs: 'docs/rule/no-inline-styles.md',\n      tests: 'test/unit/rules/no-inline-styles-test.js',\n    },\n  },\n  create(context) {\n    const options = context.options[0] || {};\n    const allowDynamicStyles =\n      options.allowDynamicStyles === undefined ? true : options.allowDynamicStyles;\n\n    return {\n      GlimmerElementNode(node) {\n        const styleAttr = node.attributes?.find((a) => a.name === 'style');\n        if (!styleAttr) {\n          return;\n        }\n\n        // If allowDynamicStyles is true, skip dynamic style values (MustacheStatement only)\n        if (allowDynamicStyles) {\n          if (styleAttr.value?.type === 'GlimmerMustacheStatement') {\n            return;\n          }\n        }\n\n        context.report({ node: styleAttr, messageId: 'noInlineStyles' });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-input-block.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow block usage of {{input}} helper',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-input-block.md',\n      templateMode: 'loose',\n    },\n    schema: [],\n    messages: { blockUsage: 'Unexpected block usage. The input helper may only be used inline.' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-input-block.js',\n      docs: 'docs/rule/no-input-block.md',\n      tests: 'test/unit/rules/no-input-block-test.js',\n    },\n  },\n  create(context) {\n    // The classic `{{input}}` helper is HBS-only — it is not an ambient\n    // strict-mode keyword. In `.gjs`/`.gts` any `{{#input}}` is necessarily\n    // a user binding (an imported or locally-declared identifier named\n    // `input`), so flagging it would corrupt the user's intent.\n    const isStrictMode = context.filename.endsWith('.gjs') || context.filename.endsWith('.gts');\n    if (isStrictMode) {\n      return {};\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'input') {\n          context.report({ node, messageId: 'blockUsage' });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-input-tagname.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow tagName attribute on {{input}} helper',\n      category: 'Best Practices',\n\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-input-tagname.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: { unexpected: 'Unexpected tagName usage on input helper.' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-input-tagname.js',\n      docs: 'docs/rule/no-input-tagname.md',\n      tests: 'test/unit/rules/no-input-tagname-test.js',\n    },\n  },\n  create(context) {\n    const isStrictMode = context.filename.endsWith('.gjs') || context.filename.endsWith('.gts');\n\n    // local name → 'Input'. Only populated in GJS/GTS via ImportDeclaration.\n    const importedComponents = new Map();\n\n    function checkCurly(node) {\n      if (!node.path) {\n        return;\n      }\n      const attrs = node.hash?.pairs || [];\n      const hasTagName = attrs.some((a) => a.key === 'tagName');\n\n      if (node.path.original === 'input' && hasTagName) {\n        context.report({ node, messageId: 'unexpected' });\n      } else if (\n        node.path.original === 'component' &&\n        node.params?.[0]?.original === 'input' &&\n        hasTagName\n      ) {\n        context.report({ node, messageId: 'unexpected' });\n      }\n    }\n\n    const visitors = {\n      GlimmerElementNode(node) {\n        const hasTagName = node.attributes?.some((a) => a.name === '@tagName');\n        if (!hasTagName) {\n          return;\n        }\n        if (isStrictMode) {\n          // In GJS/GTS: only flag if explicitly imported from @ember/component\n          if (importedComponents.has(node.tag)) {\n            context.report({ node, messageId: 'unexpected' });\n          }\n        } else {\n          // In HBS: <Input ...> always resolves to the framework Input\n          if (node.tag === 'Input') {\n            context.report({ node, messageId: 'unexpected' });\n          }\n        }\n      },\n    };\n\n    if (isStrictMode) {\n      visitors.ImportDeclaration = function (node) {\n        if (node.source.value === '@ember/component') {\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'Input') {\n              importedComponents.set(specifier.local.name, 'Input');\n            }\n          }\n        }\n      };\n    } else {\n      visitors.GlimmerMustacheStatement = checkCurly;\n      visitors.GlimmerSubExpression = checkCurly;\n    }\n\n    return visitors;\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-invalid-aria-attributes.js",
    "content": "const { aria } = require('aria-query');\n\nfunction isBoolean(value) {\n  return value === 'true' || value === 'false';\n}\n\nfunction isNumeric(value) {\n  if (typeof value !== 'string' || value === '') {\n    return false;\n  }\n  return !Number.isNaN(Number(value));\n}\n\n// In aria-query 5.3.2, `allowundefined: true` is set only on the four\n// boolean-like ARIA state attributes — `aria-expanded`, `aria-hidden`,\n// `aria-grabbed`, `aria-selected` — whose WAI-ARIA 1.2 value tables list\n// the literal string `\"undefined\"` as a spec-valid value meaning \"state\n// is not applicable\" (e.g. https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).\n// The flag is nominally type-agnostic, but in practice this function only\n// green-lights `\"undefined\"` for that boolean-like subset; no non-boolean\n// ARIA attribute in aria-query currently sets `allowundefined`.\nfunction allowsUndefinedLiteral(attrDef, value) {\n  return value === 'undefined' && Boolean(attrDef.allowundefined);\n}\n\nfunction validateByType(attrDef, value) {\n  if (allowsUndefinedLiteral(attrDef, value)) {\n    return true;\n  }\n  switch (attrDef.type) {\n    case 'boolean': {\n      return isBoolean(value);\n    }\n    case 'tristate': {\n      return isBoolean(value) || value === 'mixed';\n    }\n    case 'string':\n    case 'id': {\n      return typeof value === 'string' && !isBoolean(value);\n    }\n    case 'idlist': {\n      return (\n        typeof value === 'string' &&\n        value.split(' ').every((token) => token.length > 0 && !isBoolean(token))\n      );\n    }\n    case 'integer': {\n      return /^-?\\d+$/.test(value);\n    }\n    case 'number': {\n      return isNumeric(value) && !isBoolean(value);\n    }\n    case 'token': {\n      // aria-query stores boolean values as actual booleans; stringify for comparison.\n      // The string literal 'undefined' that appears in some values arrays (e.g.\n      // aria-orientation) passes through this check naturally — no special-casing.\n      const permittedValues = attrDef.values.map((v) =>\n        typeof v === 'boolean' ? v.toString() : v\n      );\n      return permittedValues.includes(value);\n    }\n    case 'tokenlist': {\n      return value.split(' ').every((token) => attrDef.values.includes(token.toLowerCase()));\n    }\n    default: {\n      return true;\n    }\n  }\n}\n\nfunction isValidAriaValue(attrName, value) {\n  const attrDef = aria.get(attrName);\n  if (!attrDef) {\n    return true;\n  }\n  return validateByType(attrDef, value);\n}\n\nfunction getExpectedTypeDescription(attrName) {\n  const attrDef = aria.get(attrName);\n  if (!attrDef) {\n    return 'a valid value';\n  }\n  switch (attrDef.type) {\n    case 'tristate': {\n      return 'a boolean or the string \"mixed\".';\n    }\n    case 'token': {\n      const vals = attrDef.values.map((v) => (typeof v === 'boolean' ? v.toString() : v));\n      return `a single token from the following: ${vals.join(', ')}.`;\n    }\n    case 'tokenlist': {\n      return `a list of one or more tokens from the following: ${attrDef.values.join(', ')}.`;\n    }\n    case 'idlist': {\n      return 'a list of strings that represent DOM element IDs (idlist)';\n    }\n    case 'id': {\n      return 'a string that represents a DOM element ID';\n    }\n    case 'integer': {\n      return 'an integer.';\n    }\n    default: {\n      return `a ${attrDef.type}.`;\n    }\n  }\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow invalid aria-* attributes',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-aria-attributes.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noInvalidAriaAttribute: 'Invalid ARIA attribute: {{attribute}}',\n      invalidAriaAttributeValue: 'The value for {{attribute}} must be {{expectedType}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-invalid-aria-attributes.js',\n      docs: 'docs/rule/no-invalid-aria-attributes.md',\n      tests: 'test/unit/rules/no-invalid-aria-attributes-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerAttrNode(node) {\n        if (!node.name.startsWith('aria-')) {\n          return;\n        }\n\n        // Check for unknown ARIA attribute\n        if (!aria.has(node.name)) {\n          context.report({\n            node,\n            messageId: 'noInvalidAriaAttribute',\n            data: { attribute: node.name },\n          });\n          return;\n        }\n\n        // Skip value validation for dynamic values (MustacheStatement, ConcatStatement)\n        if (\n          !node.value ||\n          node.value.type === 'GlimmerMustacheStatement' ||\n          node.value.type === 'GlimmerConcatStatement'\n        ) {\n          return;\n        }\n\n        // Validate value for text node values\n        if (node.value.type === 'GlimmerTextNode') {\n          const value = node.value.chars;\n          if (!isValidAriaValue(node.name, value)) {\n            context.report({\n              node,\n              messageId: 'invalidAriaAttributeValue',\n              data: {\n                attribute: node.name,\n                expectedType: getExpectedTypeDescription(node.name),\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-invalid-interactive.js",
    "content": "'use strict';\n\nconst { isNativeElement } = require('../utils/is-native-element');\nconst { isHtmlInteractiveContent } = require('../utils/html-interactive-content');\nconst { INTERACTIVE_ROLES } = require('../utils/interactive-roles');\n\nfunction hasAttr(node, name) {\n  return node.attributes?.some((a) => a.name === name);\n}\n\nfunction getTextAttr(node, name) {\n  const attr = node.attributes?.find((a) => a.name === name);\n  if (attr?.value?.type === 'GlimmerTextNode') {\n    return attr.value.chars;\n  }\n  return undefined;\n}\n\nconst DISALLOWED_DOM_EVENTS = new Set([\n  // Mouse events:\n  'click',\n  'dblclick',\n  'mousedown',\n  'mousemove',\n  'mouseover',\n  'mouseout',\n  'mouseup',\n  // Keyboard events:\n  'keydown',\n  'keypress',\n  'keyup',\n]);\n\nconst ELEMENT_ALLOWED_EVENTS = {\n  form: new Set(['submit', 'reset', 'change']),\n  img: new Set(['load', 'error']),\n};\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow non-interactive elements with interactive handlers',\n      category: 'Accessibility',\n      templateMode: 'both',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-interactive.md',\n    },\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          additionalInteractiveTags: { type: 'array', items: { type: 'string' } },\n          ignoredTags: { type: 'array', items: { type: 'string' } },\n          ignoreTabindex: { type: 'boolean' },\n          ignoreUsemap: { type: 'boolean' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      noInvalidInteractive:\n        'Non-interactive element <{{tagName}}> should not have interactive handler \"{{handler}}\".',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-invalid-interactive.js',\n      docs: 'docs/rule/no-invalid-interactive.md',\n      tests: 'test/unit/rules/no-invalid-interactive-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const options = context.options[0] || {};\n    const additionalInteractiveTags = new Set(options.additionalInteractiveTags || []);\n    const ignoredTags = new Set(options.ignoredTags || []);\n    const ignoreTabindex = options.ignoreTabindex || false;\n    const ignoreUsemap = options.ignoreUsemap || false;\n\n    function isInteractive(node) {\n      const tag = node.tag?.toLowerCase();\n      if (!tag) {\n        return false;\n      }\n\n      if (additionalInteractiveTags.has(tag)) {\n        return true;\n      }\n\n      // HTML §3.2.5.2.7 interactive content (authoritative for content-model;\n      // handles label/button/etc. + conditional a[href], input[!hidden],\n      // img[usemap], audio/video[controls]).\n      if (isHtmlInteractiveContent(node, getTextAttr, { ignoreUsemap })) {\n        return true;\n      }\n\n      // <canvas> — not in §3.2.5.2.7 but upstream ember-template-lint treats\n      // it as interactive (canvas is commonly wired for drawing/game UI where\n      // event handlers are expected). Preserved for upstream parity.\n      if (tag === 'canvas') {\n        return true;\n      }\n\n      // Check role\n      const role = getTextAttr(node, 'role');\n      if (role && INTERACTIVE_ROLES.has(role)) {\n        return true;\n      }\n\n      // Check tabindex\n      if (!ignoreTabindex && hasAttr(node, 'tabindex')) {\n        return true;\n      }\n\n      // Check contenteditable. Per HTML spec, valid keywords are \"true\",\n      // \"false\", \"plaintext-only\", and the empty string (which is the default\n      // state, equivalent to \"true\"). So the attribute enables editing unless\n      // its value is \"false\".\n      if (hasAttr(node, 'contenteditable')) {\n        const ce = getTextAttr(node, 'contenteditable');\n        if (ce === undefined || ce === null || ce.trim().toLowerCase() !== 'false') {\n          return true;\n        }\n      }\n\n      // Check usemap (only on img/object)\n      if (!ignoreUsemap && (tag === 'img' || tag === 'object') && hasAttr(node, 'usemap')) {\n        return true;\n      }\n\n      return false;\n    }\n\n    return {\n      // eslint-disable-next-line complexity\n      GlimmerElementNode(node) {\n        const tag = node.tag?.toLowerCase();\n        if (!tag) {\n          return;\n        }\n        if (ignoredTags.has(tag)) {\n          return;\n        }\n\n        // Skip if element is interactive\n        if (isInteractive(node)) {\n          return;\n        }\n\n        // Only analyze native HTML / SVG / MathML elements. Skip components\n        // (including tag names shadowed by in-scope bindings) and custom\n        // elements — their a11y contracts are author-defined.\n        if (!isNativeElement(node, sourceCode)) {\n          return;\n        }\n\n        const allowedEvents = ELEMENT_ALLOWED_EVENTS[tag];\n\n        // Check attributes\n        for (const attr of node.attributes || []) {\n          const attrName = attr.name?.toLowerCase();\n          if (!attrName || attrName.startsWith('@')) {\n            continue;\n          }\n\n          const isDynamic =\n            attr.value?.type === 'GlimmerMustacheStatement' ||\n            attr.value?.type === 'GlimmerConcatStatement';\n          if (!isDynamic) {\n            continue;\n          }\n\n          const isOnAttr = attrName.startsWith('on') && attrName.length > 2;\n          const event = isOnAttr ? attrName.slice(2) : null;\n\n          // Allow element-specific events (e.g. submit/reset/change on form, load/error on img)\n          if (isOnAttr && event && allowedEvents?.has(event)) {\n            continue;\n          }\n\n          const isActionHelper =\n            attr.value?.type === 'GlimmerMustacheStatement' &&\n            attr.value.path?.original === 'action';\n\n          // Flag {{action}} helper used in any attribute on a non-interactive element\n          if (isActionHelper) {\n            context.report({\n              node,\n              messageId: 'noInvalidInteractive',\n              data: { tagName: tag, handler: attrName },\n            });\n            continue;\n          }\n\n          // Flag disallowed DOM events (click, mousedown, keydown, etc.) with dynamic values\n          if (isOnAttr && DISALLOWED_DOM_EVENTS.has(event)) {\n            context.report({\n              node,\n              messageId: 'noInvalidInteractive',\n              data: { tagName: tag, handler: attrName },\n            });\n          }\n        }\n\n        // Check modifiers\n        for (const mod of node.modifiers || []) {\n          const modName = mod.path?.original;\n\n          if (modName === 'on') {\n            const eventParam = mod.params?.[0];\n            const event =\n              eventParam?.type === 'GlimmerStringLiteral' ? eventParam.value : undefined;\n\n            // Allow element-specific events\n            if (event && allowedEvents?.has(event)) {\n              continue;\n            }\n            // Allow non-disallowed events (scroll, copy, toggle, pause, etc.)\n            if (event && !DISALLOWED_DOM_EVENTS.has(event)) {\n              continue;\n            }\n\n            context.report({\n              node,\n              messageId: 'noInvalidInteractive',\n              data: { tagName: tag, handler: '{{on}}' },\n            });\n          } else if (modName === 'action') {\n            // Determine the event from on= hash param (default: 'click')\n            let event = 'click';\n            const onPair = mod.hash?.pairs?.find((p) => p.key === 'on');\n            if (onPair) {\n              event = onPair.value?.value || onPair.value?.original || 'click';\n            }\n\n            // Allow element-specific events\n            if (allowedEvents?.has(event)) {\n              continue;\n            }\n\n            context.report({\n              node,\n              messageId: 'noInvalidInteractive',\n              data: { tagName: tag, handler: '{{action}}' },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-invalid-link-text.js",
    "content": "const DISALLOWED_LINK_TEXTS = new Set(['click here', 'more info', 'read more', 'more']);\n\nfunction getTextContentResult(node) {\n  if (node.type === 'GlimmerTextNode') {\n    return { text: node.chars.replaceAll('&nbsp;', ' '), hasDynamic: false };\n  }\n  if (\n    node.type === 'GlimmerMustacheStatement' ||\n    node.type === 'GlimmerSubExpression' ||\n    node.type === 'GlimmerBlockStatement'\n  ) {\n    return { text: '', hasDynamic: true };\n  }\n  if (node.type === 'GlimmerElementNode' && node.children) {\n    let text = '';\n    let hasDynamic = false;\n    for (const child of node.children) {\n      const result = getTextContentResult(child);\n      text += result.text;\n      if (result.hasDynamic) {\n        hasDynamic = true;\n      }\n    }\n    return { text, hasDynamic };\n  }\n  return { text: '', hasDynamic: false };\n}\n\nfunction isDynamicValue(value) {\n  return value?.type === 'GlimmerMustacheStatement' || value?.type === 'GlimmerConcatStatement';\n}\n\n/**\n * Checks aria-labelledby and aria-label attributes.\n * Returns:\n *   { skip: true }                          — has valid accessible name, skip element\n *   { report: true, text: string }          — aria-label is itself a disallowed text, report it\n *   { skip: false }                         — no valid aria override, check text content\n */\nfunction checkAriaAttributes(attrs) {\n  const ariaLabelledby = attrs.find((a) => a.name === 'aria-labelledby');\n  if (ariaLabelledby) {\n    if (isDynamicValue(ariaLabelledby.value)) {\n      return { skip: true };\n    }\n    if (ariaLabelledby.value?.type === 'GlimmerTextNode') {\n      if (ariaLabelledby.value.chars.trim().length > 0) {\n        return { skip: true }; // valid non-empty labelledby\n      }\n    }\n    // empty aria-labelledby → fall through\n    return { skip: false };\n  }\n\n  const ariaLabel = attrs.find((a) => a.name === 'aria-label');\n  if (ariaLabel) {\n    if (isDynamicValue(ariaLabel.value)) {\n      return { skip: true };\n    }\n    if (ariaLabel.value?.type === 'GlimmerTextNode') {\n      const val = ariaLabel.value.chars.replaceAll('&nbsp;', ' ').toLowerCase().trim();\n      if (val.length > 0 && !DISALLOWED_LINK_TEXTS.has(val)) {\n        return { skip: true }; // valid aria-label\n      }\n      if (val.length > 0) {\n        return { skip: true, report: true, text: val }; // aria-label itself is disallowed\n      }\n    }\n  }\n\n  return { skip: false };\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow invalid or uninformative link text content',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-link-text.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowEmptyLinks: { type: 'boolean' },\n          linkComponents: { type: 'array', items: { type: 'string' } },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      invalidText:\n        'Link text \"{{text}}\" is not descriptive. Use meaningful text that describes the link destination.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-invalid-link-text.js',\n      docs: 'docs/rule/no-invalid-link-text.md',\n      tests: 'test/unit/rules/no-invalid-link-text-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const allowEmptyLinks = options.allowEmptyLinks || false;\n    const customLinkComponents = options.linkComponents || [];\n\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // In HBS, LinkTo always refers to Ember's router link component.\n    // In GJS/GTS, LinkTo must be explicitly imported from '@ember/routing'.\n    // local alias → true (any truthy value marks it as a tracked link component)\n    const importedLinkComponents = new Map();\n\n    const linkTags = new Set(['a', ...customLinkComponents]);\n    if (!isStrictMode) {\n      linkTags.add('LinkTo');\n    }\n\n    function checkLinkContent(node, children) {\n      const attrs = node.attributes || [];\n\n      // Skip if aria-hidden=\"true\"\n      const ariaHidden = attrs.find((a) => a.name === 'aria-hidden');\n      if (ariaHidden?.value?.type === 'GlimmerTextNode' && ariaHidden.value.chars === 'true') {\n        return;\n      }\n\n      // Skip if hidden attribute present\n      if (attrs.some((a) => a.name === 'hidden')) {\n        return;\n      }\n\n      const ariaResult = checkAriaAttributes(attrs);\n      if (ariaResult.report) {\n        context.report({ node, messageId: 'invalidText', data: { text: ariaResult.text } });\n        return;\n      }\n      if (ariaResult.skip) {\n        return;\n      }\n\n      // If the link contains any non-TextNode child, content is dynamic/opaque — don't flag.\n      const childList = children || [];\n      const allTextNodes = childList.every((child) => child.type === 'GlimmerTextNode');\n      if (!allTextNodes) {\n        return;\n      }\n\n      // Concatenate text content (only TextNode children at this point).\n      let fullText = '';\n      for (const child of childList) {\n        fullText += child.chars.replaceAll('&nbsp;', ' ');\n      }\n\n      const normalized = fullText.trim().toLowerCase().replaceAll(/\\s+/g, ' ');\n\n      if (!normalized.replaceAll(' ', '')) {\n        if (!allowEmptyLinks) {\n          context.report({ node, messageId: 'invalidText', data: { text: '(empty)' } });\n        }\n        return;\n      }\n\n      if (DISALLOWED_LINK_TEXTS.has(normalized)) {\n        context.report({ node, messageId: 'invalidText', data: { text: normalized } });\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === '@ember/routing') {\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'LinkTo') {\n              importedLinkComponents.set(specifier.local.name, true);\n              linkTags.add(specifier.local.name);\n            }\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (!linkTags.has(node.tag)) {\n          return;\n        }\n        checkLinkContent(node, node.children);\n      },\n\n      GlimmerBlockStatement(node) {\n        if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'link-to') {\n          checkLinkContent(node, node.program?.body);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-invalid-link-title.js",
    "content": "/**\n * Get all static text content from children of a node, lowercased and trimmed.\n */\nfunction getChildTextContents(children) {\n  const texts = [];\n  for (const child of children || []) {\n    if (child.type === 'GlimmerTextNode') {\n      const trimmed = child.chars.toLowerCase().trim();\n      if (trimmed.length > 0) {\n        texts.push(trimmed);\n      }\n    }\n  }\n  return texts;\n}\n\n/**\n * Check if any link text contains any of the title values.\n */\nfunction hasInvalidLinkTitle(children, titleValues) {\n  const linkTexts = getChildTextContents(children);\n  return linkTexts.some((linkText) => titleValues.some((title) => linkText.includes(title)));\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow invalid title attributes on link elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-link-title.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noInvalidLinkTitle: 'Link title attribute should not be the same as link text or empty.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-invalid-link-title.js',\n      docs: 'docs/rule/no-invalid-link-title.md',\n      tests: 'test/unit/rules/no-invalid-link-title-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // In HBS, <LinkTo> always refers to Ember's router link component.\n    // In GJS/GTS, LinkTo must be explicitly imported from '@ember/routing'.\n    // local alias → true (any truthy value marks it as a tracked link component)\n    const importedLinkComponents = new Map();\n\n    const linkTags = new Set(['a']);\n    if (!isStrictMode) {\n      linkTags.add('LinkTo');\n    }\n\n    // eslint-disable-next-line complexity\n    function checkElementNode(node) {\n      if (!linkTags.has(node.tag)) {\n        return;\n      }\n      // Determine if this tag should be treated as <LinkTo> for @title handling\n      const isLinkTo = node.tag === 'LinkTo' || importedLinkComponents.has(node.tag);\n\n      const titleAttr = node.attributes.find(\n        (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'title'\n      );\n      const titleArgAttr = isLinkTo\n        ? node.attributes.find((attr) => attr.type === 'GlimmerAttrNode' && attr.name === '@title')\n        : null;\n\n      // Get title attribute text value\n      let titleAttrValue;\n      if (titleAttr && titleAttr.value && titleAttr.value.type === 'GlimmerTextNode') {\n        titleAttrValue = titleAttr.value.chars;\n      }\n\n      // Get @title argument text value (LinkTo only)\n      let titleArgValue;\n      if (titleArgAttr && titleArgAttr.value && titleArgAttr.value.type === 'GlimmerTextNode') {\n        titleArgValue = titleArgAttr.value.chars;\n      }\n\n      // Collect all title values (lowercased, trimmed)\n      const titleValues = [titleAttrValue, isLinkTo ? titleArgValue : null]\n        .filter((v) => typeof v === 'string')\n        .map((v) => v.toLowerCase().trim());\n\n      // Error if both title and @title are specified on LinkTo\n      if (isLinkTo && titleAttrValue !== undefined && titleArgValue !== undefined) {\n        context.report({\n          node: titleAttr || node,\n          messageId: 'noInvalidLinkTitle',\n        });\n        return;\n      }\n\n      // Check empty title\n      if (titleValues.includes('')) {\n        context.report({\n          node: titleAttr || node,\n          messageId: 'noInvalidLinkTitle',\n        });\n        return;\n      }\n\n      // Check if title is included in link text\n      if (titleValues.length > 0 && hasInvalidLinkTitle(node.children, titleValues)) {\n        context.report({\n          node: titleAttr || titleArgAttr || node,\n          messageId: 'noInvalidLinkTitle',\n        });\n      }\n    }\n\n    function checkBlockStatement(node) {\n      if (\n        !node.path ||\n        node.path.type !== 'GlimmerPathExpression' ||\n        node.path.original !== 'link-to'\n      ) {\n        return;\n      }\n\n      // Find title in hash pairs\n      let titleValue;\n      if (node.hash && node.hash.pairs) {\n        const titlePair = node.hash.pairs.find((pair) => pair.key === 'title');\n        if (titlePair && titlePair.value) {\n          if (titlePair.value.type === 'GlimmerStringLiteral') {\n            titleValue = titlePair.value.value;\n          } else if (titlePair.value.type === 'GlimmerTextNode') {\n            titleValue = titlePair.value.chars;\n          }\n        }\n      }\n\n      if (typeof titleValue !== 'string') {\n        return;\n      }\n\n      const normalizedTitle = titleValue.toLowerCase().trim();\n\n      if (!normalizedTitle) {\n        context.report({\n          node,\n          messageId: 'noInvalidLinkTitle',\n        });\n        return;\n      }\n\n      if (hasInvalidLinkTitle(node.program && node.program.body, [normalizedTitle])) {\n        context.report({\n          node,\n          messageId: 'noInvalidLinkTitle',\n        });\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (!isStrictMode) {\n          return;\n        }\n        if (node.source.value === '@ember/routing') {\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'LinkTo') {\n              importedLinkComponents.set(specifier.local.name, true);\n              linkTags.add(specifier.local.name);\n            }\n          }\n        }\n      },\n      GlimmerElementNode: checkElementNode,\n      GlimmerBlockStatement: checkBlockStatement,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-invalid-meta.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow invalid meta tags',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-meta.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      invalidCharset: 'Meta charset should be \"utf-8\". Found: \"{{charset}}\".',\n      metaRefreshRedirect: 'A meta redirect should not have a delay value greater than zero.',\n      metaRefreshDelay: 'A meta refresh should have a delay greater than 72000 seconds.',\n      viewportUserScalable: 'A meta viewport should not restrict user-scalable.',\n      viewportMaximumScale: 'A meta viewport should not set a maximum scale on content.',\n      metaMissingContent:\n        'A meta content attribute must be defined if the name, property, itemprop, or http-equiv attribute is defined.',\n      metaMissingIdentifier:\n        'A meta content attribute cannot be defined if the name, property, itemprop, nor the http-equiv attributes are defined.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-invalid-meta.js',\n      docs: 'docs/rule/no-invalid-meta.md',\n      tests: 'test/unit/rules/no-invalid-meta-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      // eslint-disable-next-line complexity\n      GlimmerElementNode(node) {\n        if (node.tag !== 'meta') {\n          return;\n        }\n\n        function findAttr(name) {\n          return node.attributes.find((a) => a.type === 'GlimmerAttrNode' && a.name === name);\n        }\n\n        function getAttrText(name) {\n          const attr = findAttr(name);\n          if (attr && attr.value && attr.value.type === 'GlimmerTextNode') {\n            return attr.value.chars;\n          }\n          return undefined;\n        }\n\n        const hasCharset = Boolean(findAttr('charset'));\n        const hasName = Boolean(findAttr('name'));\n        const hasHttpEquiv = Boolean(findAttr('http-equiv'));\n        const hasProperty = Boolean(findAttr('property'));\n        const hasItemprop = Boolean(findAttr('itemprop'));\n        const hasContent = Boolean(findAttr('content'));\n        const hasIdentifier = hasName || hasHttpEquiv || hasProperty || hasItemprop;\n\n        // Check for invalid charset value\n        const charsetValue = getAttrText('charset');\n        if (hasCharset && charsetValue) {\n          const normalizedCharset = charsetValue.toLowerCase().replaceAll('-', '');\n          if (normalizedCharset !== 'utf8') {\n            context.report({\n              node,\n              messageId: 'invalidCharset',\n              data: { charset: charsetValue },\n            });\n          }\n        }\n\n        // Check: identifier present but no content\n        if (hasIdentifier && !hasContent && !hasCharset) {\n          context.report({\n            node,\n            messageId: 'metaMissingContent',\n          });\n        }\n\n        // Check: content present but no identifier or charset\n        if (hasContent && !hasIdentifier && !hasCharset) {\n          context.report({\n            node,\n            messageId: 'metaMissingIdentifier',\n          });\n        }\n\n        // Check content-based validations\n        const contentValue = getAttrText('content');\n\n        if (hasContent && typeof contentValue === 'string') {\n          // http-equiv=\"refresh\" checks\n          if (hasHttpEquiv) {\n            if (contentValue.includes(';')) {\n              // Redirect: should not have delay > 0\n              if (contentValue.charAt(0) !== '0') {\n                context.report({\n                  node,\n                  messageId: 'metaRefreshRedirect',\n                });\n              }\n            } else {\n              // Plain refresh: delay should be > 72000\n              const delay = Number.parseInt(contentValue, 10);\n              if (delay <= 72_000) {\n                context.report({\n                  node,\n                  messageId: 'metaRefreshDelay',\n                });\n              }\n            }\n          }\n\n          // Viewport checks\n          const userScalableRegExp = /user-scalable(\\s*?)=(\\s*?)no/gim;\n          if (userScalableRegExp.test(contentValue)) {\n            context.report({\n              node,\n              messageId: 'viewportUserScalable',\n            });\n          }\n\n          if (contentValue.includes('maximum-scale')) {\n            context.report({\n              node,\n              messageId: 'viewportMaximumScale',\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-invalid-role.js",
    "content": "const { roles } = require('aria-query');\n\n// Valid ARIA roles = concrete (non-abstract) entries from aria-query, plus the\n// WAI-ARIA 1.3 draft roles that aria-query 5.3.2 doesn't yet ship. The\n// ARIA 1.2 base roles, DPUB-ARIA (doc-*), and Graphics-ARIA (graphics-*) all\n// come from aria-query. `associationlist*`, `comment`, and `suggestion` are in\n// the current ARIA 1.3 editor's draft (https://w3c.github.io/aria/) but not\n// yet in aria-query, so they're listed here until the next aria-query release\n// adds them.\nconst ARIA_13_DRAFT_ROLES = [\n  'associationlist',\n  'associationlistitemkey',\n  'associationlistitemvalue',\n  'comment',\n  'suggestion',\n];\nconst VALID_ROLES = new Set([\n  ...[...roles.keys()].filter((role) => !roles.get(role).abstract),\n  ...ARIA_13_DRAFT_ROLES,\n]);\n\n// Elements with semantic meaning that should not be given role=\"presentation\" or role=\"none\"\n// List from https://developer.mozilla.org/en-US/docs/Web/HTML/Element\nconst SEMANTIC_ELEMENTS = new Set([\n  'a',\n  'abbr',\n  'applet',\n  'area',\n  'audio',\n  'b',\n  'bdi',\n  'bdo',\n  'blockquote',\n  'br',\n  'button',\n  'caption',\n  'cite',\n  'code',\n  'col',\n  'colgroup',\n  'data',\n  'datalist',\n  'dd',\n  'del',\n  'details',\n  'dfn',\n  'dialog',\n  'dir',\n  'dl',\n  'dt',\n  'em',\n  'embed',\n  'fieldset',\n  'figcaption',\n  'figure',\n  'form',\n  'hr',\n  'i',\n  'iframe',\n  'input',\n  'ins',\n  'kbd',\n  'label',\n  'legend',\n  'main',\n  'map',\n  'mark',\n  'menu',\n  'menuitem',\n  'meter',\n  'noembed',\n  'object',\n  'ol',\n  'optgroup',\n  'option',\n  'output',\n  'p',\n  'param',\n  'pre',\n  'progress',\n  'q',\n  'rb',\n  'rp',\n  'rt',\n  'rtc',\n  'ruby',\n  's',\n  'samp',\n  'select',\n  'small',\n  'source',\n  'strong',\n  'sub',\n  'summary',\n  'sup',\n  'table',\n  'tbody',\n  'td',\n  'textarea',\n  'tfoot',\n  'th',\n  'thead',\n  'time',\n  'tr',\n  'track',\n  'tt',\n  'u',\n  'ul',\n  'var',\n  'video',\n  'wbr',\n]);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow invalid ARIA roles',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-invalid-role.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          catchNonexistentRoles: { type: 'boolean' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      invalid: \"Invalid ARIA role '{{role}}'. Must be a valid ARIA role.\",\n      presentationOnSemantic:\n        'The role \"{{role}}\" should not be used on the semantic element <{{tag}}>.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-invalid-role.js',\n      docs: 'docs/rule/no-invalid-role.md',\n      tests: 'test/unit/rules/no-invalid-role-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const catchNonexistentRoles = options.catchNonexistentRoles !== false; // default true\n\n    return {\n      GlimmerElementNode(node) {\n        const roleAttr = node.attributes?.find((a) => a.name === 'role');\n        if (!roleAttr || roleAttr.value?.type !== 'GlimmerTextNode') {\n          return;\n        }\n\n        // ARIA role attribute is a whitespace-separated list of tokens\n        // (role-fallback pattern per ARIA 1.2 §5.4). An empty / whitespace-\n        // only value supplies zero tokens — flag as `role=\"\"` to catch the\n        // authoring mistake (matches jsx-a11y / vue-a11y).\n        const raw = roleAttr.value.chars.trim();\n        if (!raw) {\n          context.report({\n            node: roleAttr,\n            messageId: 'invalid',\n            data: { role: '' },\n          });\n          return;\n        }\n\n        // Validate each token. Keep the original casing alongside the\n        // normalized (lowercase) form so reported tokens preserve author\n        // intent — validation is case-insensitive, the ERROR MESSAGE isn't.\n        const rawTokens = raw.split(/\\s+/u);\n        const tokens = rawTokens.map((t) => t.toLowerCase());\n\n        if (catchNonexistentRoles) {\n          const invalidIdx = tokens.findIndex((token) => !VALID_ROLES.has(token));\n          if (invalidIdx !== -1) {\n            context.report({\n              node: roleAttr,\n              messageId: 'invalid',\n              data: { role: rawTokens[invalidIdx] },\n            });\n            return;\n          }\n        }\n\n        // Flag presentation/none only when it's the FIRST recognised role per\n        // WAI-ARIA §4.1 fallback semantics — UAs walk the token list and use\n        // the first role they recognise; subsequent tokens are author-provided\n        // fallbacks that never take effect if the first is recognised. So\n        // `role=\"button presentation\"` resolves to `button` at runtime and\n        // should NOT flag. `role=\"xxyxyz presentation\"` resolves to\n        // `presentation` (unknown tokens are skipped) and SHOULD flag on a\n        // semantic element. Case-insensitivity inherits from HTML per §4.1:\n        // https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors_roles\n        const firstRecognisedRole = tokens.find((t) => VALID_ROLES.has(t));\n        if (\n          (firstRecognisedRole === 'presentation' || firstRecognisedRole === 'none') &&\n          SEMANTIC_ELEMENTS.has(node.tag)\n        ) {\n          context.report({\n            node: roleAttr,\n            messageId: 'presentationOnSemantic',\n            data: { role: firstRecognisedRole, tag: node.tag },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-jsx-attributes.js",
    "content": "const fixMap = {\n  acceptCharset: 'accept-charset',\n  srcSet: 'srcset',\n  accessKey: 'accesskey',\n  allowFullScreen: 'allowfullscreen',\n  allowTransparency: 'allowtransparency',\n  autoComplete: 'autocomplete',\n  autoFocus: 'autofocus',\n  autoPlay: 'autoplay',\n  cellPadding: 'cellpadding',\n  cellSpacing: 'cellspacing',\n  charSet: 'charset',\n  className: 'class',\n  contentEditable: 'contenteditable',\n  contextMenu: 'contextmenu',\n  crossOrigin: 'crossorigin',\n  dateTime: 'datetime',\n  encType: 'enctype',\n  formAction: 'formaction',\n  formEncType: 'formenctype',\n  formMethod: 'formmethod',\n  formNoValidate: 'formnovalidate',\n  formTarget: 'formtarget',\n  frameBorder: 'frameborder',\n  httpEquiv: 'http-equiv',\n  inputMode: 'inputmode',\n  keyType: 'keytype',\n  noValidate: 'novalidate',\n  marginHeight: 'marginheight',\n  marginWidth: 'marginwidth',\n  maxLength: 'maxlength',\n  minLength: 'minlength',\n  radioGroup: 'radiogroup',\n  readOnly: 'readonly',\n  rowSpan: 'rowspan',\n  colSpan: 'colspan',\n  spellCheck: 'spellcheck',\n  srcDoc: 'srcdoc',\n  tabIndex: 'tabindex',\n  useMap: 'usemap',\n};\n\nconst camelCaseAttributes = Object.keys(fixMap);\n\nfunction getMessage(name) {\n  if (name === 'className') {\n    return `Attribute, ${name}, does not assign the 'class' attribute as it would in JSX.  To assign the 'class' attribute, set the 'class' attribute, instead of 'className'. In HTML, all attributes are valid, but 'className' doesn't do anything.`;\n  }\n\n  return `Incorrect html attribute name detected - \"${name}\", is probably unintended. Attributes in HTML are kebeb case.`;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow JSX-style camelCase attributes',\n      category: 'Possible Errors',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-jsx-attributes.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-jsx-attributes.js',\n      docs: 'docs/rule/no-jsx-attributes.md',\n      tests: 'test/unit/rules/no-jsx-attributes-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerAttrNode(node) {\n        const key = node.name;\n        const isJSXProbably = camelCaseAttributes.includes(key);\n\n        if (!isJSXProbably) {\n          return;\n        }\n\n        context.report({\n          node,\n          message: getMessage(key),\n          fix: fixMap[key]\n            ? (fixer) => {\n                const sourceCode = context.sourceCode;\n                const text = sourceCode.getText(node);\n                const valueMatch = text.match(/^[^=]+(=.*)?$/);\n                const value = valueMatch && valueMatch[1] ? valueMatch[1] : '';\n                return fixer.replaceText(node, `${fixMap[key]}${value}`);\n              }\n            : null,\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-let-reference.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow referencing let variables in \\\\<template\\\\>',\n      category: 'Ember Octane',\n      recommendedGjs: true,\n      recommendedGts: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-let-reference.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      'no-let': 'update-able variables are not supported in templates, reference a const variable',\n    },\n  },\n\n  create: (context) => {\n    const sourceCode = context.sourceCode;\n\n    function checkIfWritableReferences(node, scope) {\n      const ref = scope.references.find((v) => v.identifier === node);\n      if (!ref) {\n        return;\n      }\n      if (ref.resolved?.identifiers.some((i) => ['let', 'var'].includes(i.parent.parent.kind))) {\n        context.report({ node, messageId: 'no-let' });\n      }\n    }\n\n    return {\n      GlimmerPathExpression(node) {\n        checkIfWritableReferences(node.head, sourceCode.getScope(node));\n      },\n\n      GlimmerElementNode(node) {\n        // glimmer element is in its own scope, need to get upper scope\n        const scope = sourceCode.getScope(node.parent);\n        // GlimmerElementNode is not referenced, instead use tag name parts[0]\n        checkIfWritableReferences(node.parts[0], scope);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-link-to-positional-params.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow positional params in LinkTo component',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-link-to-positional-params.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noLinkToPositionalParams: 'Positional params in LinkTo are deprecated. Use @route instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-link-to-positional-params.js',\n      docs: 'docs/rule/no-link-to-positional-params.md',\n      tests: 'test/unit/rules/no-link-to-positional-params-test.js',\n    },\n  },\n\n  create(context) {\n    function checkForPositionalParams(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'link-to' &&\n        node.params &&\n        node.params.length > 0\n      ) {\n        context.report({\n          node,\n          messageId: 'noLinkToPositionalParams',\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        checkForPositionalParams(node);\n      },\n\n      GlimmerBlockStatement(node) {\n        checkForPositionalParams(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-link-to-tagname.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow tagName attribute on LinkTo component',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-link-to-tagname.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noLinkToTagname:\n        '@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-link-to-tagname.js',\n      docs: 'docs/rule/no-link-to-tagname.md',\n      tests: 'test/unit/rules/no-link-to-tagname-test.js',\n    },\n  },\n\n  create(context) {\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n\n    // In HBS, `LinkTo` almost always refers to Ember's router link component; a\n    // user-defined `link-to` could shadow it, but detecting that in classic HBS\n    // would require resolver-level info the rule doesn't have.\n    // In GJS/GTS, LinkTo must be explicitly imported from '@ember/routing'\n    // (and may be renamed, e.g. `import { LinkTo as Link } from '@ember/routing'`).\n    // local alias → true\n    const importedLinkComponents = new Map();\n\n    function isLinkToComponent(node) {\n      if (node.type !== 'GlimmerElementNode') {\n        return false;\n      }\n      if (isStrictMode) {\n        return importedLinkComponents.has(node.tag);\n      }\n      return node.tag === 'LinkTo' || node.tag === 'link-to';\n    }\n\n    function checkHashPairsForTagName(node) {\n      if (!node.hash || !node.hash.pairs) {\n        return;\n      }\n      const tagNamePair = node.hash.pairs.find((pair) => pair.key === 'tagName');\n      if (tagNamePair) {\n        context.report({\n          node: tagNamePair,\n          messageId: 'noLinkToTagname',\n        });\n      }\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (!isStrictMode) {\n          return;\n        }\n        if (node.source.value !== '@ember/routing') {\n          return;\n        }\n        for (const specifier of node.specifiers) {\n          if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'LinkTo') {\n            importedLinkComponents.set(specifier.local.name, true);\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        if (!isLinkToComponent(node)) {\n          return;\n        }\n\n        const tagNameAttr = node.attributes.find(\n          (attr) => attr.type === 'GlimmerAttrNode' && attr.name === '@tagName'\n        );\n\n        if (tagNameAttr) {\n          context.report({\n            node: tagNameAttr,\n            messageId: 'noLinkToTagname',\n          });\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (node.path?.original === 'link-to') {\n          checkHashPairsForTagName(node);\n        }\n      },\n\n      GlimmerBlockStatement(node) {\n        if (node.path?.original === 'link-to') {\n          checkHashPairsForTagName(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-log.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow {{log}} in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-log.md',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unexpected: 'Unexpected log statement in template.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-log.js',\n      docs: 'docs/rule/no-log.md',\n      tests: 'test/unit/rules/no-log-test.js',\n    },\n  },\n\n  create(context) {\n    // `log` is an ambient strict-mode keyword (Glimmer CALL_KEYWORDS), so\n    // `{{log foo}}` works in .gjs/.gts without an import. Still flag it — but\n    // skip when `log` resolves to a binding (JS import/const, or template\n    // block param). ember-eslint-parser registers template block params in\n    // scope, so a single getScope walk covers both.\n    const sourceCode = context.sourceCode;\n\n    function isInScope(node, name) {\n      if (!sourceCode) {\n        return false;\n      }\n      try {\n        let scope = sourceCode.getScope(node);\n        while (scope) {\n          if (scope.variables.some((v) => v.name === name)) {\n            return true;\n          }\n          scope = scope.upper;\n        }\n      } catch {\n        // getScope not available in .hbs-only mode\n      }\n      return false;\n    }\n\n    function checkForLog(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'log' &&\n        !isInScope(node, 'log')\n      ) {\n        context.report({ node, messageId: 'unexpected' });\n      }\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        checkForLog(node);\n      },\n      GlimmerMustacheStatement(node) {\n        checkForLog(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-model-argument-in-route-templates.js",
    "content": "const path = require('path');\n\n/**\n * Determine whether a file path corresponds to a route template.\n * Mirrors ember-template-lint's is-route-template.js heuristic (and the\n * duplicate in template-no-outlet-outside-routes.js):\n *  - If the path is unknown, assume it could be a route (default-lint).\n *  - Partials (basename starts with '-') are not routes.\n *  - Classic component templates (<app>/templates/components/) are not routes.\n *  - Co-located component templates (<app>/components/) are not routes.\n *\n * Note: GJS/GTS files can be route templates (e.g. app/routes/foo.gjs), so\n * we do not gate on file extension.\n */\nfunction isRouteTemplate(filePath) {\n  if (typeof filePath !== 'string') {\n    return true; // unknown — assume it could be a route\n  }\n\n  const normalized = filePath.replaceAll('\\\\', '/');\n  const baseName = path.basename(normalized);\n\n  if (baseName.startsWith('-')) {\n    return false;\n  }\n\n  return (\n    !/^[^/]+\\/templates\\/components\\//.test(normalized) && // classic component\n    !/^[^/]+\\/components\\//.test(normalized) // co-located component template\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow @model argument in route templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-model-argument-in-route-templates.md',\n      templateMode: 'loose',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noModelArgumentInRouteTemplates:\n        'Unexpected @model in route template. Use this.model in the controller or component instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-model-argument-in-route-templates.js',\n      docs: 'docs/rule/no-model-argument-in-route-templates.md',\n      tests: 'test/unit/rules/no-model-argument-in-route-templates-test.js',\n    },\n  },\n\n  create(context) {\n    const routeTemplate = isRouteTemplate(context.filename);\n\n    if (!routeTemplate) {\n      return {};\n    }\n\n    return {\n      GlimmerPathExpression(node) {\n        // Check for @model usage\n        if (node.original === '@model' || node.original.startsWith('@model.')) {\n          const replacement = node.original.replace('@model', 'this.model');\n          context.report({\n            node,\n            messageId: 'noModelArgumentInRouteTemplates',\n            fix(fixer) {\n              return fixer.replaceText(node, replacement);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-multiple-empty-lines.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow multiple consecutive empty lines in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-multiple-empty-lines.md',\n      templateMode: 'both',\n    },\n    fixable: 'whitespace',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          max: {\n            type: 'integer',\n            minimum: 0,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      unexpected: 'More than {{max}} blank {{pluralizedLines}} not allowed.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-multiple-empty-lines.js',\n      docs: 'docs/rule/no-multiple-empty-lines.md',\n      tests: 'test/unit/rules/no-multiple-empty-lines-test.js',\n    },\n  },\n\n  create(context) {\n    const max = context.options[0]?.max ?? 1;\n    const sourceCode = context.sourceCode;\n\n    return {\n      Program(node) {\n        const text = sourceCode.getText();\n        const lines = text.split('\\n');\n\n        // Precompute the character offset of the start of each line.\n        const lineOffsets = [];\n        let offset = 0;\n        for (const line of lines) {\n          lineOffsets.push(offset);\n          offset += line.length + 1; // +1 for the '\\n'\n        }\n\n        // Swallow the final newline, as some editors add it automatically\n        // and we don't want it to cause an issue.\n        const effectiveLines = lines.length > 0 && lines.at(-1) === '' ? lines.slice(0, -1) : lines;\n\n        let emptyCount = 0;\n        let firstEmptyLine = -1;\n\n        function reportExcess(endIndex) {\n          const startLine = firstEmptyLine + max;\n          const endLine = endIndex;\n\n          // Remove the excess empty lines: keep `max` empty lines,\n          // remove everything from the start of the (max+1)-th empty\n          // line to the start of the next non-empty line (or end of content).\n          const rangeStart = lineOffsets[firstEmptyLine + max];\n          const rangeEnd = endIndex < lines.length ? lineOffsets[endIndex] : text.length;\n\n          context.report({\n            loc: {\n              start: { line: startLine + 1, column: 0 },\n              end: { line: endLine + 1, column: 0 },\n            },\n            messageId: 'unexpected',\n            data: {\n              max,\n              pluralizedLines: max === 1 ? 'line' : 'lines',\n            },\n            fix(fixer) {\n              return fixer.replaceTextRange([rangeStart, rangeEnd], '');\n            },\n          });\n        }\n\n        for (const [index, line] of effectiveLines.entries()) {\n          if (line.trim() === '') {\n            if (emptyCount === 0) {\n              firstEmptyLine = index;\n            }\n            emptyCount++;\n          } else {\n            if (emptyCount > max) {\n              reportExcess(index);\n            }\n            emptyCount = 0;\n            firstEmptyLine = -1;\n          }\n        }\n\n        // Handle trailing empty lines at the end of the effective content\n        if (emptyCount > max) {\n          reportExcess(effectiveLines.length);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-mut-helper.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of (mut) helper',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-mut-helper.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          setterAlternative: { type: 'string' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      noMutHelper: '{{message}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-mut-helper.js',\n      docs: 'docs/rule/no-mut-helper.md',\n      tests: 'test/unit/rules/no-mut-helper-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const rawSetterAlternative = options.setterAlternative;\n    // If the user already wrapped their expression in `{{...}}`, strip it so we\n    // don't end up with `{{{{...}}}}` in the error message.\n    const setterAlternative =\n      typeof rawSetterAlternative === 'string'\n        ? rawSetterAlternative.replace(/^{{([\\S\\s]*)}}$/, '$1').trim()\n        : rawSetterAlternative;\n    const message = setterAlternative\n      ? `Do not use the (mut) helper. Consider using a JS action or {{${setterAlternative}}} instead.`\n      : 'Do not use the (mut) helper. Use regular setters or actions instead.';\n\n    function checkNode(node) {\n      if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'mut') {\n        context.report({\n          node,\n          messageId: 'noMutHelper',\n          data: { message },\n        });\n      }\n    }\n\n    return {\n      GlimmerSubExpression: checkNode,\n      GlimmerMustacheStatement: checkNode,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-negated-condition.js",
    "content": "const ERROR_MESSAGE_FLIP_IF =\n  'Change `{{if (not condition)}} ... {{else}} ... {{/if}}` to `{{if condition}} ... {{else}} ... {{/if}}`.';\nconst ERROR_MESSAGE_USE_IF = 'Change `unless (not condition)` to `if condition`.';\nconst ERROR_MESSAGE_USE_UNLESS = 'Change `if (not condition)` to `unless condition`.';\nconst ERROR_MESSAGE_NEGATED_HELPER = 'Simplify unnecessary negation of helper.';\n\nconst INVERTIBLE_HELPERS = new Set(['not', 'eq', 'not-eq', 'gt', 'gte', 'lt', 'lte']);\n\nconst HELPER_INVERSIONS = {\n  not: null, // special case\n  eq: 'not-eq',\n  'not-eq': 'eq',\n  gt: 'lte',\n  gte: 'lt',\n  lt: 'gte',\n  lte: 'gt',\n};\n\nfunction isIf(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'if';\n}\n\nfunction isUnless(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'unless';\n}\n\nfunction hasNotHelper(node) {\n  return (\n    node.params?.length > 0 &&\n    node.params[0].type === 'GlimmerSubExpression' &&\n    node.params[0].path?.type === 'GlimmerPathExpression' &&\n    node.params[0].path.original === 'not'\n  );\n}\n\nfunction hasNestedFixableHelper(node) {\n  const inner = node.params[0]?.params?.[0];\n  return (\n    inner &&\n    inner.path?.type === 'GlimmerPathExpression' &&\n    INVERTIBLE_HELPERS.has(inner.path.original)\n  );\n}\n\nfunction escapeRegExp(string) {\n  return string.replaceAll(/[$()*+.?[\\\\\\]^{|}]/g, '\\\\$&');\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow negated conditions in if/unless',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-negated-condition.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          simplifyHelpers: { type: 'boolean' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      flipIf: ERROR_MESSAGE_FLIP_IF,\n      useIf: ERROR_MESSAGE_USE_IF,\n      useUnless: ERROR_MESSAGE_USE_UNLESS,\n      negatedHelper: ERROR_MESSAGE_NEGATED_HELPER,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-negated-condition.js',\n      docs: 'docs/rule/no-negated-condition.md',\n      tests: 'test/unit/rules/no-negated-condition-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const simplifyHelpers = options.simplifyHelpers === undefined ? true : options.simplifyHelpers;\n    const sourceCode = context.sourceCode;\n\n    /**\n     * Get the source text for the inner condition of a (not ...) sub-expression,\n     * with the nested helper optionally inverted.\n     */\n    function getUnwrappedConditionText(notExpr, invertHelper) {\n      const inner = notExpr.params[0];\n      if (invertHelper && inner.path?.type === 'GlimmerPathExpression') {\n        const helperName = inner.path.original;\n        const inverted = HELPER_INVERSIONS[helperName];\n        if (inverted !== undefined) {\n          if (inverted === null) {\n            if (inner.params.length > 1) {\n              // (not (not c1 c2)) -> (or c1 c2)\n              const paramsText = inner.params.map((p) => sourceCode.getText(p)).join(' ');\n              return `(or ${paramsText})`;\n            }\n            // (not (not x)) -> just x\n            return sourceCode.getText(inner.params[0]);\n          }\n          // (not (eq a b)) -> (not-eq a b)\n          const innerText = sourceCode.getText(inner);\n          return innerText.replace(`(${helperName} `, `(${inverted} `);\n        }\n      }\n      return sourceCode.getText(inner);\n    }\n\n    /**\n     * Build a fix function for block statements.\n     */\n    function buildBlockFix(node, messageId) {\n      return function fix(fixer) {\n        const fullText = sourceCode.getText(node);\n        const keyword = node.path.original;\n        const notExpr = node.params[0];\n\n        if (messageId === 'negatedHelper') {\n          const conditionText = getUnwrappedConditionText(notExpr, true);\n          const newText = fullText.replace(sourceCode.getText(notExpr), conditionText);\n          return fixer.replaceText(node, newText);\n        }\n\n        if (messageId === 'flipIf') {\n          // {{#if (not x)}}A{{else}}B{{/if}} -> {{#if x}}B{{else}}A{{/if}}\n          const conditionText = getUnwrappedConditionText(notExpr, false);\n          const programBody = node.program.body.map((n) => sourceCode.getText(n)).join('');\n          const inverseBody = node.inverse.body.map((n) => sourceCode.getText(n)).join('');\n\n          return fixer.replaceText(\n            node,\n            `{{#${keyword} ${conditionText}}}${inverseBody}{{else}}${programBody}{{/${keyword}}}`\n          );\n        }\n\n        if (messageId === 'useIf' || messageId === 'useUnless') {\n          const newKeyword = keyword === 'unless' ? 'if' : 'unless';\n          const conditionText = getUnwrappedConditionText(notExpr, false);\n          const notExprText = escapeRegExp(sourceCode.getText(notExpr));\n          const newText = fullText\n            .replace(\n              new RegExp(`^\\\\{\\\\{#${keyword} ${notExprText}`),\n              `{{#${newKeyword} ${conditionText}`\n            )\n            .replace(new RegExp(`\\\\{\\\\{/${keyword}\\\\}\\\\}$`), `{{/${newKeyword}}}`);\n          return fixer.replaceText(node, newText);\n        }\n\n        return null;\n      };\n    }\n\n    /**\n     * Build a fix function for inline (mustache/subexpression) statements.\n     */\n    function buildInlineFix(node, messageId) {\n      return function fix(fixer) {\n        const fullText = sourceCode.getText(node);\n        const keyword = node.path.original;\n        const notExpr = node.params[0];\n\n        if (messageId === 'negatedHelper') {\n          const conditionText = getUnwrappedConditionText(notExpr, true);\n          const newText = fullText.replace(sourceCode.getText(notExpr), conditionText);\n          return fixer.replaceText(node, newText);\n        }\n\n        if (messageId === 'flipIf') {\n          const conditionText = getUnwrappedConditionText(notExpr, false);\n          const param1Text = sourceCode.getText(node.params[1]);\n          const param2Text = sourceCode.getText(node.params[2]);\n          const isSubExpr = node.type === 'GlimmerSubExpression';\n          const open = isSubExpr ? '(' : '{{';\n          const close = isSubExpr ? ')' : '}}';\n          return fixer.replaceText(\n            node,\n            `${open}${keyword} ${conditionText} ${param2Text} ${param1Text}${close}`\n          );\n        }\n\n        if (messageId === 'useIf' || messageId === 'useUnless') {\n          const newKeyword = keyword === 'unless' ? 'if' : 'unless';\n          const conditionText = getUnwrappedConditionText(notExpr, false);\n          const isSubExpr = node.type === 'GlimmerSubExpression';\n          const open = isSubExpr ? '(' : '{{';\n          const close = isSubExpr ? ')' : '}}';\n          const remainingParams = node.params\n            .slice(1)\n            .map((p) => sourceCode.getText(p))\n            .join(' ');\n          return fixer.replaceText(\n            node,\n            `${open}${newKeyword} ${conditionText} ${remainingParams}${close}`\n          );\n        }\n\n        return null;\n      };\n    }\n\n    // eslint-disable-next-line complexity\n    function checkNode(node) {\n      const nodeIsIf = isIf(node);\n      const nodeIsUnless = isUnless(node);\n\n      if (!nodeIsIf && !nodeIsUnless) {\n        return;\n      }\n\n      // Skip `{{else if ...}}` / `{{else unless ...}}` chains for the outer check,\n      // unless they have a fixable negated helper inside\n      if (node.type === 'GlimmerBlockStatement') {\n        const text = sourceCode.getText(node);\n        if (text.startsWith('{{else ')) {\n          if (!simplifyHelpers || !hasNotHelper(node) || !hasNestedFixableHelper(node)) {\n            return;\n          }\n          context.report({\n            node: node.params[0],\n            messageId: 'negatedHelper',\n            fix: buildBlockFix(node, 'negatedHelper'),\n          });\n          return;\n        }\n\n        // Ignore `if ... else if ...` (chained) to avoid forcing negation\n        if (\n          node.inverse?.body?.length > 0 &&\n          node.inverse.body[0].type === 'GlimmerBlockStatement' &&\n          nodeIsIf &&\n          isIf(node.inverse.body[0])\n        ) {\n          return;\n        }\n      }\n\n      if (!hasNotHelper(node)) {\n        return;\n      }\n\n      const notExpr = node.params[0];\n      const hasFixableHelper = hasNestedFixableHelper(node);\n\n      // If it's `if (not (someHelper ...))` and we can't simplify the helper,\n      // don't suggest converting to `unless` (simple-unless rule would reject it)\n      if (\n        nodeIsIf &&\n        notExpr.params?.[0]?.type === 'GlimmerSubExpression' &&\n        (!simplifyHelpers || !hasFixableHelper)\n      ) {\n        return;\n      }\n\n      // (not a b c) with multiple params — can't simply remove negation\n      if (notExpr.params?.length > 1) {\n        return;\n      }\n\n      // Determine message\n      const isIfElseBlock = node.type === 'GlimmerBlockStatement' && node.inverse?.body?.length > 0;\n      const isIfElseInline = node.type !== 'GlimmerBlockStatement' && node.params?.length === 3;\n      const shouldFlip = isIfElseBlock || isIfElseInline;\n\n      let messageId;\n      if (hasFixableHelper && simplifyHelpers) {\n        messageId = 'negatedHelper';\n      } else if (shouldFlip && nodeIsIf) {\n        messageId = 'flipIf';\n      } else if (nodeIsUnless) {\n        messageId = 'useIf';\n      } else {\n        messageId = 'useUnless';\n      }\n\n      const isBlock = node.type === 'GlimmerBlockStatement';\n      context.report({\n        node: notExpr,\n        messageId,\n        fix: isBlock ? buildBlockFix(node, messageId) : buildInlineFix(node, messageId),\n      });\n    }\n\n    return {\n      GlimmerBlockStatement: checkNode,\n      GlimmerMustacheStatement: checkNode,\n      GlimmerSubExpression: checkNode,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-nested-interactive.js",
    "content": "'use strict';\n\nconst { isHtmlInteractiveContent } = require('../utils/html-interactive-content');\nconst { INTERACTIVE_ROLES, COMPOSITE_WIDGET_CHILDREN } = require('../utils/interactive-roles');\n\nfunction hasAttr(node, name) {\n  return node.attributes?.some((a) => a.name === name);\n}\n\nfunction getTextAttr(node, name) {\n  const attr = node.attributes?.find((a) => a.name === name);\n  if (attr?.value?.type === 'GlimmerTextNode') {\n    return attr.value.chars;\n  }\n  return undefined;\n}\n\nfunction getRole(node) {\n  return getTextAttr(node, 'role');\n}\n\n// Menu submenu pattern — per WAI-ARIA APG, a `menuitem` with `aria-haspopup`\n// may own a nested `menu`. aria-query's `requiredOwnedElements` does not\n// express this \"menu-inside-menuitem\" direction, so it is handled explicitly.\nconst MENUITEM_ROLES = new Set(['menuitem', 'menuitemcheckbox', 'menuitemradio']);\n\nfunction isCompositeWidgetPattern(parentRole, childRole) {\n  if (!parentRole || !childRole) {\n    return false;\n  }\n  const allowedChildren = COMPOSITE_WIDGET_CHILDREN.get(parentRole);\n  if (allowedChildren && allowedChildren.has(childRole)) {\n    return true;\n  }\n  // Submenu: <… role=\"menuitem\"><… role=\"menu\"> …\n  if (MENUITEM_ROLES.has(parentRole) && childRole === 'menu') {\n    return true;\n  }\n  return false;\n}\n\nfunction isMenuItemNode(node) {\n  // Match all three menu-item role variants per ARIA taxonomy. `menuitem`,\n  // `menuitemcheckbox`, and `menuitemradio` are all \"menu items\" — they can\n  // carry submenus (via MENUITEM_ROLES in isCompositeWidgetPattern) and nest\n  // each other (via the nested-menuitem compat exception). Keeping both\n  // predicates symmetric avoids false positives on APG Menu Button /\n  // Menubar patterns that mix the variants.\n  return MENUITEM_ROLES.has(getTextAttr(node, 'role'));\n}\n\n// Build the element-description string used in error messages. Surfaces the\n// attribute that *makes* the element interactive when the bare tag would be\n// uninformative — e.g. `<div role=\"menu\">`, `<div contenteditable>`, or\n// `<div tabindex=\"0\">`. For self-explanatory native interactive tags\n// (button, input, a, etc.) the tag alone is returned, since adding the\n// triggering attribute would be redundant noise.\nfunction describeInteractive(node) {\n  const tag = node.tag;\n\n  const role = getTextAttr(node, 'role');\n  if (role && INTERACTIVE_ROLES.has(role)) {\n    return `${tag} role=\"${role}\"`;\n  }\n\n  if (hasAttr(node, 'contenteditable')) {\n    const ce = getTextAttr(node, 'contenteditable');\n    const normalized = typeof ce === 'string' ? ce.trim().toLowerCase() : ce;\n    if (normalized !== 'false') {\n      // Surface 'plaintext-only' as a distinct spec keyword; collapse the\n      // empty string, 'true', and the bare attribute to a uniform form.\n      if (normalized === 'plaintext-only') {\n        return `${tag} contenteditable=\"plaintext-only\"`;\n      }\n      return `${tag} contenteditable`;\n    }\n  }\n\n  // Tabindex-only interactivity: the tag (typically <div>/<span>) carries no\n  // signal on its own, so surface the tabindex value. Skip for elements that\n  // are already interactive via tag/usemap/canvas — for those the tag itself\n  // is the source of interactivity and the tabindex would be redundant.\n  if (\n    hasAttr(node, 'tabindex') &&\n    !isHtmlInteractiveContent(node, getTextAttr, { ignoreUsemap: false }) &&\n    tag !== 'canvas' &&\n    !(tag === 'object' && hasAttr(node, 'usemap'))\n  ) {\n    const tabindex = getTextAttr(node, 'tabindex');\n    return tabindex === undefined ? `${tag} tabindex` : `${tag} tabindex=\"${tabindex}\"`;\n  }\n\n  return tag;\n}\n\nfunction isAllowedDetailsChild(childNode, parentEntry) {\n  if (parentEntry.tag !== 'details') {\n    return false;\n  }\n  // Non-<summary> children are flow content in the disclosed panel — allowed.\n  // <summary> is only allowed as the first non-whitespace child of <details>.\n  if (childNode.tag !== 'summary') {\n    return true;\n  }\n  const children = parentEntry.node.children || [];\n  const firstNonWhitespace = children.find((child) => {\n    if (child.type === 'GlimmerTextNode') {\n      return child.chars.trim().length > 0;\n    }\n    return true;\n  });\n  return firstNonWhitespace === childNode;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow nested interactive elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-nested-interactive.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          additionalInteractiveTags: { type: 'array', items: { type: 'string' } },\n          ignoredTags: { type: 'array', items: { type: 'string' } },\n          ignoreTabindex: { type: 'boolean' },\n          ignoreUsemap: { type: 'boolean' },\n          ignoreUsemapAttribute: { type: 'boolean' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      nested: 'Do not nest interactive element <{{child}}> inside <{{parent}}>.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-nested-interactive.js',\n      docs: 'docs/rule/no-nested-interactive.md',\n      tests: 'test/unit/rules/no-nested-interactive-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const additionalInteractiveTags = new Set(options.additionalInteractiveTags || []);\n    const ignoredTags = new Set(options.ignoredTags || []);\n    const ignoreTabindex = options.ignoreTabindex || false;\n    const ignoreUsemap = options.ignoreUsemap || options.ignoreUsemapAttribute || false;\n\n    const interactiveStack = [];\n    // Stack for saving/restoring label interactiveChildCount across GlimmerBlock boundaries\n    const blockCountStack = [];\n\n    function isInteractive(node) {\n      const tag = node.tag?.toLowerCase();\n      if (!tag) {\n        return false;\n      }\n      if (ignoredTags.has(tag)) {\n        return false;\n      }\n      if (additionalInteractiveTags.has(tag)) {\n        return true;\n      }\n\n      // HTML §3.2.5.2.7 interactive content (authoritative for content-model\n      // nesting — handles a[href], audio/video[controls], input[!hidden],\n      // img[usemap], plus label/button/select/textarea/iframe/etc. unconditional).\n      if (isHtmlInteractiveContent(node, getTextAttr, { ignoreUsemap })) {\n        return true;\n      }\n\n      // <canvas> — not in §3.2.5.2.7 but upstream ember-template-lint treats\n      // it as interactive. Preserved for parity.\n      if (tag === 'canvas') {\n        return true;\n      }\n\n      // ARIA widget roles (author-declared interactivity — separate authority\n      // from HTML content-model: `html-interactive-content.js` speaks to the\n      // HTML §3.2.5.2.7 content model, while `interactive-roles.js` speaks to\n      // the WAI-ARIA 1.2 widget taxonomy).\n      const role = getTextAttr(node, 'role');\n      if (role && INTERACTIVE_ROLES.has(role)) {\n        return true;\n      }\n\n      // Check tabindex\n      if (!ignoreTabindex && hasAttr(node, 'tabindex')) {\n        return true;\n      }\n\n      // Check contenteditable. Per HTML spec, valid keywords are \"true\",\n      // \"false\", \"plaintext-only\", and the empty string (which is the default\n      // state, equivalent to \"true\"). So the attribute enables editing unless\n      // its value is \"false\".\n      if (hasAttr(node, 'contenteditable')) {\n        const ce = getTextAttr(node, 'contenteditable');\n        if (ce === undefined || ce === null || ce.trim().toLowerCase() !== 'false') {\n          return true;\n        }\n      }\n\n      // <object usemap> — not in HTML §3.2.5.2.7 but upstream ember-template-lint\n      // treats object+usemap as interactive (image-map behavior). Rule-level\n      // special case for upstream parity; revisit if/when HTML-AAM clarifies.\n      if (!ignoreUsemap && tag === 'object' && hasAttr(node, 'usemap')) {\n        return true;\n      }\n\n      return false;\n    }\n\n    /**\n     * Returns true if the element is interactive ONLY because of tabindex\n     * (not because of tag name, role, contenteditable, usemap, etc.)\n     * Called only after isInteractive() already returned true.\n     */\n    function isInteractiveOnlyFromTabindex(node) {\n      const tag = node.tag?.toLowerCase();\n      if (!tag) {\n        return false;\n      }\n      if (additionalInteractiveTags.has(tag)) {\n        return false;\n      }\n      if (tag === 'canvas') {\n        return false;\n      }\n      if (isHtmlInteractiveContent(node, getTextAttr, { ignoreUsemap })) {\n        return false;\n      }\n      const role = getTextAttr(node, 'role');\n      if (role && INTERACTIVE_ROLES.has(role)) {\n        return false;\n      }\n      if (hasAttr(node, 'contenteditable')) {\n        const ce = getTextAttr(node, 'contenteditable');\n        if (ce === undefined || ce === null || ce.trim().toLowerCase() !== 'false') {\n          return false;\n        }\n      }\n      if ((tag === 'img' || tag === 'object') && hasAttr(node, 'usemap')) {\n        return false;\n      }\n      return hasAttr(node, 'tabindex');\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        const currentIsInteractive = isInteractive(node);\n\n        if (currentIsInteractive && interactiveStack.length > 0) {\n          const parentEntry = interactiveStack.at(-1);\n\n          if (parentEntry.tag === 'label') {\n            // Label can contain ONE interactive child — track and flag additional ones\n            if (parentEntry.interactiveChildCount >= 1) {\n              context.report({\n                node,\n                messageId: 'nested',\n                data: { parent: parentEntry.describe, child: describeInteractive(node) },\n              });\n            }\n            parentEntry.interactiveChildCount++;\n          } else if (isAllowedDetailsChild(node, parentEntry)) {\n            // flow content in the disclosed panel, or <summary> as first child\n          } else if (isCompositeWidgetPattern(getRole(parentEntry.node), getRole(node))) {\n            // Canonical ARIA composite-widget hierarchies — e.g. option inside\n            // listbox, tab inside tablist, treeitem inside tree, row inside\n            // grid/treegrid, gridcell/columnheader/rowheader inside row,\n            // radio inside radiogroup, menu inside menuitem (submenu).\n            // Derived from aria-query's requiredOwnedElements with superClass\n            // inheritance — see lib/utils/interactive-roles.js.\n          } else if (isMenuItemNode(parentEntry.node) && isMenuItemNode(node)) {\n            // Nested menu-item nodes (any combination of menuitem /\n            // menuitemcheckbox / menuitemradio) are valid — menu/sub-menu\n            // pattern per WAI-ARIA APG. Kept for historical compat since\n            // aria-query doesn't encode this via requiredOwnedElements.\n          } else {\n            context.report({\n              node,\n              messageId: 'nested',\n              data: { parent: parentEntry.describe, child: describeInteractive(node) },\n            });\n          }\n        }\n\n        // Push interactive elements to the stack, but tabindex-only elements\n        // should not become parent interactive nodes\n        if (currentIsInteractive && !isInteractiveOnlyFromTabindex(node)) {\n          interactiveStack.push({\n            tag: node.tag,\n            node,\n            describe: describeInteractive(node),\n            interactiveChildCount: 0,\n          });\n        }\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        if (interactiveStack.length > 0 && interactiveStack.at(-1).node === node) {\n          interactiveStack.pop();\n        }\n      },\n\n      // Save/restore label interactive child count at block boundaries\n      // so that conditional branches ({{#if}}/{{else}}) are tracked independently\n      GlimmerBlock() {\n        const labelEntry = interactiveStack.length > 0 ? interactiveStack.at(-1) : null;\n        if (labelEntry && labelEntry.tag === 'label') {\n          blockCountStack.push(labelEntry.interactiveChildCount);\n        } else {\n          blockCountStack.push(null);\n        }\n      },\n\n      'GlimmerBlock:exit'() {\n        const saved = blockCountStack.pop();\n        if (saved !== null && saved !== undefined) {\n          const labelEntry = interactiveStack.length > 0 ? interactiveStack.at(-1) : null;\n          if (labelEntry && labelEntry.tag === 'label') {\n            labelEntry.interactiveChildCount = saved;\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-nested-landmark.js",
    "content": "const { LANDMARK_ROLES } = require('../utils/landmark-roles');\n\nconst LANDMARK_ELEMENTS = new Set(['header', 'aside', 'footer', 'form', 'main', 'nav']);\n\nconst EQUIVALENT_ROLE = {\n  aside: 'complementary',\n  footer: 'contentinfo',\n  header: 'banner',\n  main: 'main',\n  nav: 'navigation',\n  section: 'region',\n};\n\nfunction isLandmark(node) {\n  // Check if element is inherently a landmark\n  if (LANDMARK_ELEMENTS.has(node.tag)) {\n    return true;\n  }\n\n  // Check if element has a landmark role\n  const roleAttr = node.attributes?.find((a) => a.name === 'role');\n  if (roleAttr?.value?.type === 'GlimmerTextNode') {\n    return LANDMARK_ROLES.has(roleAttr.value.chars);\n  }\n\n  return false;\n}\n\nfunction getLandmarkType(node) {\n  // If node has an explicit role attribute, use that\n  const roleAttr = node.attributes?.find((a) => a.name === 'role');\n  if (roleAttr?.value?.type === 'GlimmerTextNode') {\n    return roleAttr.value.chars;\n  }\n  // Otherwise, use the equivalent role for the tag\n  return EQUIVALENT_ROLE[node.tag] || node.tag;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow nested landmark elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-nested-landmark.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      nested: 'Landmark elements should not be nested within other landmarks.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-nested-landmark.js',\n      docs: 'docs/rule/no-nested-landmark.md',\n      tests: 'test/unit/rules/no-nested-landmark-test.js',\n    },\n  },\n\n  create(context) {\n    const landmarkStack = [];\n\n    return {\n      GlimmerElementNode(node) {\n        const isCurrentLandmark = isLandmark(node);\n\n        if (isCurrentLandmark) {\n          const currentType = getLandmarkType(node);\n          // Check if any ancestor landmark has the same type\n          for (const ancestor of landmarkStack) {\n            if (getLandmarkType(ancestor) === currentType) {\n              context.report({\n                node,\n                messageId: 'nested',\n              });\n              break;\n            }\n          }\n          landmarkStack.push(node);\n        }\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        if (isLandmark(node)) {\n          landmarkStack.pop();\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-nested-splattributes.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow nested ...attributes usage',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-nested-splattributes.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noNestedSplattributes:\n        'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-nested-splattributes.js',\n      docs: 'docs/rule/no-nested-splattributes.md',\n      tests: 'test/unit/rules/no-nested-splattributes-test.js',\n    },\n  },\n\n  create(context) {\n    const splattributesStack = [];\n\n    return {\n      GlimmerElementNode(node) {\n        const hasSplattributes = node.attributes.some(\n          (attr) => attr.type === 'GlimmerAttrNode' && attr.name === '...attributes'\n        );\n\n        if (hasSplattributes) {\n          if (splattributesStack.length > 0) {\n            // Found ...attributes on an element nested inside another element with ...attributes\n            const attr = node.attributes.find(\n              (a) => a.type === 'GlimmerAttrNode' && a.name === '...attributes'\n            );\n            context.report({\n              node: attr,\n              messageId: 'noNestedSplattributes',\n            });\n          }\n          splattributesStack.push(node);\n        }\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        if (splattributesStack.length > 0 && splattributesStack.at(-1) === node) {\n          splattributesStack.pop();\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-obscure-array-access.js",
    "content": "const DIGIT_REGEXP = /^\\[?\\d+]?$/;\n\n/**\n * Extract the target path and string key from a path with numeric segments.\n * E.g. \"this.list.0.name\" → [\"this.list\", \"0.name\"]\n * E.g. \"this.list.[0]\" → [\"this.list\", \"0\"]\n * E.g. \"@foo.0.bar\" → [\"@foo\", \"0.bar\"]\n *\n * @param {string} original The original path string\n * @returns {[string, string]} A tuple of [targetPath, stringKey]\n */\nfunction getHelperParams(original) {\n  const parts = original.split('.');\n  const firstDigitIndex = parts.findIndex((part) => DIGIT_REGEXP.test(part));\n\n  if (firstDigitIndex === -1) {\n    return null;\n  }\n\n  const targetPath = parts.slice(0, firstDigitIndex).join('.');\n  // Strip brackets from digit segments: [0] → 0\n  const keyParts = parts.slice(firstDigitIndex).map((part) => part.replace(/^\\[(\\d+)]$/, '$1'));\n  const stringKey = keyParts.join('.');\n\n  return [targetPath, stringKey];\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow obscure array access patterns like `objectPath.@each.property`',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-obscure-array-access.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noObscureArrayAccess:\n        'Unexpected obscure array access pattern \"{{path}}\". Use computed properties or helpers instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-obscure-array-access.js',\n      docs: 'docs/rule/no-obscure-array-access.md',\n      tests: 'test/unit/rules/no-obscure-array-access-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerPathExpression(node) {\n        const path = node.original;\n        const sourcePath = context.sourceCode.getText(node);\n        // Check for @each or [] in paths — these are structural and not autofixable\n        if (path && (path.includes('.@each.') || path.includes('.[].'))) {\n          context.report({\n            node,\n            messageId: 'noObscureArrayAccess',\n            data: { path: sourcePath },\n          });\n          return;\n        }\n        // Check for numeric path segments (e.g., foo.0.bar) or bracket notation (e.g., foo.[0])\n        if (node.tail && node.tail.some((segment) => /^\\d+$/.test(segment))) {\n          const params = getHelperParams(path);\n          context.report({\n            node,\n            messageId: 'noObscureArrayAccess',\n            data: { path: sourcePath },\n            fix: params ? buildFix(context, node, params) : undefined,\n          });\n        }\n      },\n    };\n  },\n};\n\n/**\n * Build a fix function for a numeric path expression.\n *\n * @param {import('eslint').Rule.RuleContext} context\n * @param {import('estree').Node} node The GlimmerPathExpression node\n * @param {[string, string]} params The [targetPath, stringKey] tuple\n * @returns {(fixer: import('eslint').Rule.RuleFixer) => import('eslint').Rule.Fix}\n */\nfunction buildFix(context, node, params) {\n  const [target, key] = params;\n  const parent = node.parent;\n\n  // Case 1: PathExpression is the path of a MustacheStatement (e.g., {{this.list.0.name}})\n  // Replace the entire mustache inner content with: get target \"key\"\n  if (\n    parent &&\n    parent.type === 'GlimmerMustacheStatement' &&\n    parent.path === node &&\n    parent.params.length === 0 &&\n    (!parent.hash || parent.hash.pairs.length === 0)\n  ) {\n    return (fixer) => {\n      // The mustache is {{path}} — replace just the path portion\n      // The source text of the MustacheStatement includes {{ and }}\n      const mustacheSource = context.sourceCode.getText(parent);\n      const isTriple = mustacheSource.startsWith('{{{');\n      const openLen = isTriple ? 3 : 2;\n      const closeLen = isTriple ? 3 : 2;\n      const innerStart = parent.range[0] + openLen;\n      const innerEnd = parent.range[1] - closeLen;\n\n      return fixer.replaceTextRange([innerStart, innerEnd], `get ${target} \"${key}\"`);\n    };\n  }\n\n  // Case 2: PathExpression is a param or hash value or any other context\n  // Wrap with (get target \"key\")\n  return (fixer) => {\n    return fixer.replaceText(node, `(get ${target} \"${key}\")`);\n  };\n}\n"
  },
  {
    "path": "lib/rules/template-no-obsolete-elements.js",
    "content": "const OBSOLETE = [\n  'acronym',\n  'applet',\n  'basefont',\n  'bgsound',\n  'big',\n  'blink',\n  'center',\n  'dir',\n  'font',\n  'frame',\n  'frameset',\n  'isindex',\n  'keygen',\n  'listing',\n  'marquee',\n  'menuitem',\n  'multicol',\n  'nextid',\n  'nobr',\n  'noembed',\n  'noframes',\n  'param',\n  'plaintext',\n  'rb',\n  'rtc',\n  'spacer',\n  'strike',\n  'tt',\n  'xmp',\n];\n\nfunction hasBindingInScopeChain(scope, name) {\n  for (let s = scope; s; s = s.upper) {\n    if (s.set && s.set.has(name)) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow obsolete HTML elements',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-obsolete-elements.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: { obsolete: '<{{element}}> is obsolete, use modern alternatives' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-obsolete-elements.js',\n      docs: 'docs/rule/no-obsolete-elements.md',\n      tests: 'test/unit/rules/no-obsolete-elements-test.js',\n    },\n  },\n  create(context) {\n    const obsolete = new Set(OBSOLETE);\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerElementNode(node) {\n        if (!obsolete.has(node.tag)) {\n          return;\n        }\n        // Use the parent's scope so that the element's own `as |x|` params\n        // (which attach a block scope to this node) don't shadow its own tag\n        // name. e.g. `<marquee as |marquee|>` must still flag the outer tag.\n        const scope = sourceCode.getScope(node.parent);\n        if (hasBindingInScopeChain(scope, node.tag)) {\n          return;\n        }\n        context.report({ node, messageId: 'obsolete', data: { element: node.tag } });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-only-default-slot.js",
    "content": "const ERROR_MESSAGE =\n  'Only default slot used — prefer direct block content without <:default> for clarity and simplicity.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow using only the default slot',\n      category: 'Stylistic Issues',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-only-default-slot.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-only-default-slot.js',\n      docs: 'docs/rule/no-only-default-slot.md',\n      tests: 'test/unit/rules/no-only-default-slot-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag === ':default') {\n          // Find the parent element node\n          const parent = node.parent;\n\n          if (parent && parent.type === 'GlimmerElementNode') {\n            // Check if parent has only one child (the :default node)\n            if (parent.children && parent.children.length === 1) {\n              context.report({\n                node,\n                message: ERROR_MESSAGE,\n                fix(fixer) {\n                  const sourceCode = context.sourceCode;\n                  // Replace the :default node with its children\n                  if (node.children && node.children.length > 0) {\n                    const childrenText = node.children\n                      .map((child) => sourceCode.getText(child))\n                      .join('');\n                    return fixer.replaceText(node, childrenText);\n                  } else {\n                    // If no children, just remove the :default node\n                    return fixer.remove(node);\n                  }\n                },\n              });\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-outlet-outside-routes.js",
    "content": "const path = require('path');\n\n/**\n * Determine whether a file path corresponds to a route template.\n * Mirrors ember-template-lint's is-route-template.js heuristic:\n *  - If the path is unknown, assume it could be a route (don't report).\n *  - Partials (basename starts with '-') are not routes.\n *  - Classic component templates (<app>/templates/components/) are not routes.\n *  - Co-located component templates (<app>/components/) are not routes.\n *\n * Note: GJS/GTS files can be route templates (e.g. app/routes/foo.gjs).\n */\nfunction isRouteTemplate(filePath) {\n  if (typeof filePath !== 'string') {\n    return true; // unknown — assume it could be a route\n  }\n\n  const normalized = filePath.replaceAll('\\\\', '/');\n  const baseName = path.basename(normalized);\n\n  if (baseName.startsWith('-')) {\n    return false;\n  }\n\n  return (\n    !/^[^/]+\\/templates\\/components\\//.test(normalized) && // classic component\n    !/^[^/]+\\/components\\//.test(normalized) // co-located component template\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow {{outlet}} outside of route templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-outlet-outside-routes.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noOutletOutsideRoutes: 'outlet should only be used in route templates.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-outlet-outside-routes.js',\n      docs: 'docs/rule/no-outlet-outside-routes.md',\n      tests: 'test/unit/rules/no-outlet-outside-routes-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const filename = context.filename;\n\n    const routeTemplate = isRouteTemplate(filename);\n\n    function isJsScopeVariable(node) {\n      if (!sourceCode || !node.path?.original) {\n        return false;\n      }\n      const name = node.path.original;\n      try {\n        let scope = sourceCode.getScope(node);\n        while (scope) {\n          if (scope.variables.some((v) => v.name === name)) {\n            return true;\n          }\n          scope = scope.upper;\n        }\n      } catch {\n        // sourceCode.getScope may not be available in .hbs-only mode; ignore.\n      }\n      return false;\n    }\n\n    function checkForOutlet(node) {\n      if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'outlet') {\n        if (!routeTemplate && !isJsScopeVariable(node)) {\n          context.report({\n            node,\n            messageId: 'noOutletOutsideRoutes',\n          });\n        }\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement: checkForOutlet,\n      GlimmerBlockStatement: checkForOutlet,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-page-title-component.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow usage of ember-page-title component',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-page-title-component.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noPageTitle: 'Use the `pageTitle` helper instead of the <PageTitle> component.',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag === 'PageTitle') {\n          context.report({\n            node,\n            messageId: 'noPageTitle',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-passed-in-event-handlers.js",
    "content": "// Comprehensive Ember event handler names\n// Note: mouseMove, mouseEnter, and mouseLeave are intentionally excluded —\n// they are native DOM events that do not have corresponding Ember\n// classic-event aliases on components.\nconst EMBER_EVENTS = new Set([\n  'touchStart',\n  'touchMove',\n  'touchEnd',\n  'touchCancel',\n  'keyDown',\n  'keyUp',\n  'keyPress',\n  'mouseDown',\n  'mouseUp',\n  'contextMenu',\n  'click',\n  'doubleClick',\n  'focusIn',\n  'focusOut',\n  'submit',\n  'change',\n  'input',\n  'dragStart',\n  'drag',\n  'dragEnter',\n  'dragLeave',\n  'dragOver',\n  'dragEnd',\n  'drop',\n]);\n\nfunction isEventName(name) {\n  return EMBER_EVENTS.has(name);\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow passing event handlers directly as component arguments',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-passed-in-event-handlers.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          ignore: {\n            type: 'object',\n            additionalProperties: {\n              type: 'array',\n              items: { type: 'string' },\n            },\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      unexpected:\n        'Event handler \"@{{name}}\" should not be passed as a component argument. Use the `on` modifier instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-passed-in-event-handlers.js',\n      docs: 'docs/rule/no-passed-in-event-handlers.md',\n      tests: 'test/unit/rules/no-passed-in-event-handlers-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const ignoreConfig = options.ignore || {};\n\n    return {\n      GlimmerElementNode(node) {\n        // Only check component invocations (PascalCase)\n        if (!/^[A-Z]/.test(node.tag)) {\n          return;\n        }\n        // Skip built-in Input/Textarea\n        if (node.tag === 'Input' || node.tag === 'Textarea') {\n          return;\n        }\n\n        if (!node.attributes) {\n          return;\n        }\n\n        const ignoredAttrs = ignoreConfig[node.tag] || [];\n\n        for (const attr of node.attributes) {\n          if (!attr.name || !attr.name.startsWith('@')) {\n            continue;\n          }\n          const argName = attr.name.slice(1);\n\n          if (ignoredAttrs.includes(argName)) {\n            continue;\n          }\n\n          if (isEventName(argName)) {\n            context.report({\n              node: attr,\n              messageId: 'unexpected',\n              data: { name: argName },\n            });\n          }\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        const path = node.path;\n        if (!path || path.type !== 'GlimmerPathExpression') {\n          return;\n        }\n        // Skip built-in input/textarea\n        if (path.original === 'input' || path.original === 'textarea') {\n          return;\n        }\n        // Check hash pairs for event handler names\n        if (!node.hash || !node.hash.pairs) {\n          return;\n        }\n        const ignoredAttrs = ignoreConfig[path.original] || [];\n\n        for (const pair of node.hash.pairs) {\n          if (ignoredAttrs.includes(pair.key)) {\n            continue;\n          }\n          if (isEventName(pair.key)) {\n            context.report({\n              node: pair,\n              messageId: 'unexpected',\n              data: { name: pair.key },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-pointer-down-event-binding.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow pointer down event bindings',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-pointer-down-event-binding.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unexpected: 'Avoid binding to a pointer `down` event; bind to a pointer `up` event instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-pointer-down-event-binding.js',\n      docs: 'docs/rule/no-pointer-down-event-binding.md',\n      tests: 'test/unit/rules/no-pointer-down-event-binding-test.js',\n    },\n  },\n\n  create(context) {\n    const DOWN_EVENTS = new Set(['mousedown', 'onmousedown', 'pointerdown', 'onpointerdown']);\n\n    function isDownEvent(name) {\n      return DOWN_EVENTS.has(name.toLowerCase());\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        // Check for onmousedown/onpointerdown HTML attributes\n        if (node.attributes) {\n          for (const attr of node.attributes) {\n            if (attr.name && attr.name.startsWith('on') && isDownEvent(attr.name)) {\n              context.report({ node: attr, messageId: 'unexpected' });\n            }\n          }\n        }\n\n        // Check modifiers: {{on \"mousedown\"}} and {{action ... on=\"mousedown\"}}\n        if (node.modifiers) {\n          for (const modifier of node.modifiers) {\n            if (modifier.path?.type !== 'GlimmerPathExpression') {\n              continue;\n            }\n\n            if (modifier.path.original === 'on' && modifier.params?.length > 0) {\n              const eventParam = modifier.params[0];\n              if (eventParam.type === 'GlimmerStringLiteral' && isDownEvent(eventParam.value)) {\n                context.report({ node: modifier, messageId: 'unexpected' });\n              }\n            }\n\n            if (modifier.path.original === 'action') {\n              const onPair = modifier.hash?.pairs?.find((p) => p.key === 'on');\n              if (\n                onPair &&\n                onPair.value?.type === 'GlimmerStringLiteral' &&\n                isDownEvent(onPair.value.value)\n              ) {\n                context.report({ node: modifier, messageId: 'unexpected' });\n              }\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-positional-data-test-selectors.js",
    "content": "const BUILT_INS = new Set([\n  'action',\n  'array',\n  'component',\n  'concat',\n  'debugger',\n  'each',\n  'each-in',\n  'fn',\n  'get',\n  'hasBlock',\n  'has-block',\n  'has-block-params',\n  'hash',\n  'if',\n  'input',\n  'let',\n  'link-to',\n  'loc',\n  'log',\n  'mount',\n  'mut',\n  'on',\n  'outlet',\n  'partial',\n  'query-params',\n  'textarea',\n  'unbound',\n  'unless',\n  'with',\n  '-in-element',\n  'in-element',\n]);\n\nfunction checkNode(node, context) {\n  if (!node.path || BUILT_INS.has(node.path.original)) {\n    return;\n  }\n\n  if (!node.params) {\n    return;\n  }\n\n  for (const param of node.params) {\n    if (\n      param.type === 'GlimmerPathExpression' &&\n      param.original &&\n      param.original.startsWith('data-test-')\n    ) {\n      context.report({\n        node,\n        messageId: 'noPositionalDataTest',\n        fix(fixer) {\n          // Convert the positional param to a hash pair by appending =true.\n          // e.g. `data-test-foo` becomes `data-test-foo=true`\n          const sourceCode = context.sourceCode;\n          const paramText = sourceCode.getText(param);\n          return fixer.replaceText(param, `${paramText}=true`);\n        },\n      });\n      return;\n    }\n  }\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow positional data-test-* params in curly invocations',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-positional-data-test-selectors.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noPositionalDataTest:\n        'Passing a `data-test-*` positional param to a curly invocation should be avoided.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-positional-data-test-selectors.js',\n      docs: 'docs/rule/no-positional-data-test-selectors.md',\n      tests: 'test/unit/rules/no-positional-data-test-selectors-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerMustacheStatement(node) {\n        checkNode(node, context);\n      },\n\n      GlimmerBlockStatement(node) {\n        checkNode(node, context);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-positive-tabindex.js",
    "content": "/**\n * Check a tabindex attribute value and return the violation type, if any.\n * Returns null if safe, 'positive' if the value is a positive integer,\n * or 'mustBeNegativeNumeric' if the value is non-numeric/dynamic/boolean.\n */\nfunction getTabindexViolation(attrValue) {\n  if (!attrValue) {\n    return null;\n  }\n\n  // Handle simple text values like tabindex=\"0\" or tabindex=\"-1\"\n  if (attrValue.type === 'GlimmerTextNode') {\n    const value = Number.parseInt(attrValue.chars, 10);\n    if (Number.isNaN(value)) {\n      return 'mustBeNegativeNumeric';\n    }\n    return value > 0 ? 'positive' : null;\n  }\n\n  // Handle mustache statements like tabindex={{-1}} or tabindex={{someProperty}}\n  if (attrValue.type === 'GlimmerMustacheStatement') {\n    const path = attrValue.path;\n\n    if (path.type === 'GlimmerNumberLiteral') {\n      return Number.parseInt(path.original, 10) > 0 ? 'positive' : null;\n    }\n    if (path.type === 'GlimmerStringLiteral') {\n      const value = Number.parseInt(path.original, 10);\n      if (Number.isNaN(value)) {\n        return 'mustBeNegativeNumeric';\n      }\n      return value > 0 ? 'positive' : null;\n    }\n\n    // Handle conditional expressions like {{if this.show -1 0}}\n    if (\n      path.type === 'GlimmerPathExpression' &&\n      (path.original === 'if' || path.original === 'unless')\n    ) {\n      return getConditionalTabindexViolation(attrValue.params);\n    }\n\n    // Any other dynamic value (variable, boolean, etc.) is not verifiably safe\n    return 'mustBeNegativeNumeric';\n  }\n\n  // Handle concat statements like tabindex=\"{{-1}}\" or tabindex=\"{{false}}\"\n  if (attrValue.type === 'GlimmerConcatStatement') {\n    const parts = attrValue.parts || [];\n    if (parts.length > 0 && parts[0].type === 'GlimmerMustacheStatement') {\n      return getTabindexViolation(parts[0]);\n    }\n    return 'mustBeNegativeNumeric';\n  }\n\n  return 'mustBeNegativeNumeric';\n}\n\n/**\n * Check that all branches of a conditional (if/unless) expression are safe.\n */\nfunction getConditionalTabindexViolation(params) {\n  if (!params) {\n    return 'mustBeNegativeNumeric';\n  }\n\n  // Check the value branches (params[1] and optionally params[2])\n  for (let i = 1; i < params.length && i < 3; i++) {\n    const param = params[i];\n    if (param.type === 'GlimmerNumberLiteral') {\n      if (Number.parseInt(param.original, 10) > 0) {\n        return 'positive';\n      }\n    } else if (param.type === 'GlimmerStringLiteral') {\n      const val = Number.parseInt(param.original, 10);\n      if (Number.isNaN(val)) {\n        return 'mustBeNegativeNumeric';\n      }\n      if (val > 0) {\n        return 'positive';\n      }\n    } else {\n      // Dynamic value in branch — not verifiably safe\n      return 'mustBeNegativeNumeric';\n    }\n  }\n\n  return null;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow positive tabindex values',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-positive-tabindex.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      positive: 'Avoid positive integer values for tabindex.',\n      mustBeNegativeNumeric: 'Tabindex values must be negative numeric.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-positive-tabindex.js',\n      docs: 'docs/rule/no-positive-tabindex.md',\n      tests: 'test/unit/rules/no-positive-tabindex-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const tabindexAttr = node.attributes?.find((attr) => attr.name === 'tabindex');\n\n        if (!tabindexAttr || !tabindexAttr.value) {\n          return;\n        }\n\n        const violation = getTabindexViolation(tabindexAttr.value);\n        if (violation) {\n          context.report({\n            node: tabindexAttr,\n            messageId: violation,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-potential-path-strings.js",
    "content": "const FINE_SYMBOLS = ['|', '/', '\\\\'];\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow potential path strings in attribute values',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-potential-path-strings.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noPotentialPathStrings:\n        'Potential path in attribute string detected. Did you mean {{{{path}}}}?',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-potential-path-strings.js',\n      docs: 'docs/rule/no-potential-path-strings.md',\n      tests: 'test/unit/rules/no-potential-path-strings-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerAttrNode(node) {\n        if (!node.value || node.value.type !== 'GlimmerTextNode') {\n          return;\n        }\n\n        const chars = node.value.chars;\n        const hasSpecialPrefix = chars.startsWith('this.') || chars.startsWith('@');\n\n        if (hasSpecialPrefix && !FINE_SYMBOLS.some((symbol) => chars.includes(symbol))) {\n          context.report({\n            node: node.value,\n            messageId: 'noPotentialPathStrings',\n            data: { path: chars },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-quoteless-attributes.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require quotes on all attribute values',\n      category: 'Style',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-quoteless-attributes.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      missing: '{{type}} {{name}} should be either quoted or wrapped in mustaches',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-quoteless-attributes.js',\n      docs: 'docs/rule/no-quoteless-attributes.md',\n      tests: 'test/unit/rules/no-quoteless-attributes-test.js',\n    },\n  },\n  create(context) {\n    const sourceCode = context.sourceCode;\n    return {\n      GlimmerAttrNode(node) {\n        if (node.value?.type !== 'GlimmerTextNode') {\n          return;\n        }\n\n        const valueSource = sourceCode.getText(node.value);\n        if (valueSource.length === 0 || valueSource[0] === '\"' || valueSource[0] === \"'\") {\n          return;\n        }\n\n        const type = node.name?.startsWith('@') ? 'Argument' : 'Attribute';\n        context.report({\n          node,\n          messageId: 'missing',\n          data: { type, name: node.name },\n          fix(fixer) {\n            return fixer.replaceText(node, `${node.name}=\"${node.value.chars}\"`);\n          },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-redundant-fn.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary usage of (fn) helper',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-redundant-fn.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      redundant:\n        'Unnecessary use of (fn) helper. Pass the function directly instead: {{suggestion}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-redundant-fn.js',\n      docs: 'docs/rule/no-redundant-fn.md',\n      tests: 'test/unit/rules/no-redundant-fn-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    /**\n     * Check whether `fn` in the template resolves to a local/imported binding.\n     * If it does, the user has shadowed Ember's built-in `fn` helper and we\n     * should not flag the usage.\n     */\n    function isFnShadowed(node) {\n      const head = node.path?.head;\n      if (!head) {\n        return false;\n      }\n      const scope = sourceCode.getScope(node);\n      const ref = scope.references.find((r) => r.identifier === head);\n      return ref?.resolved !== null && ref?.resolved !== undefined;\n    }\n\n    function checkFnUsage(node) {\n      // Check if this is an (fn) call with only one argument (the function itself)\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'fn' &&\n        node.params &&\n        node.params.length === 1 &&\n        !node.hash?.pairs?.length\n      ) {\n        // In gjs/gts, `fn` may be shadowed by a local import — skip if so.\n        if (isFnShadowed(node)) {\n          return;\n        }\n        const param = node.params[0];\n        const paramText =\n          param.type === 'GlimmerPathExpression'\n            ? param.original\n            : context.sourceCode.getText(param);\n\n        context.report({\n          node,\n          messageId: 'redundant',\n          data: {\n            suggestion: paramText,\n          },\n          fix(fixer) {\n            if (node.type === 'GlimmerMustacheStatement') {\n              return fixer.replaceTextRange(node.range, `{{${paramText}}}`);\n            }\n            // GlimmerSubExpression: (fn this.handleClick) → this.handleClick\n            return fixer.replaceTextRange(node.range, paramText);\n          },\n        });\n      }\n    }\n\n    return {\n      GlimmerSubExpression(node) {\n        checkFnUsage(node);\n      },\n\n      GlimmerMustacheStatement(node) {\n        checkFnUsage(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-redundant-role.js",
    "content": "const { roles } = require('aria-query');\nconst { LANDMARK_ROLES } = require('../utils/landmark-roles');\n\nconst DEFAULT_CONFIG = {\n  checkAllHTMLElements: true,\n};\n\nfunction parseConfig(config) {\n  if (config === true) {\n    return DEFAULT_CONFIG;\n  }\n  return { ...DEFAULT_CONFIG, ...config };\n}\n\nfunction createErrorMessageLandmarkElement(element, role) {\n  return `Use of redundant or invalid role: ${role} on <${element}> detected. If a landmark element is used, any role provided will either be redundant or incorrect.`;\n}\n\nfunction createErrorMessageAnyElement(element, role) {\n  return `Use of redundant or invalid role: ${role} on <${element}> detected.`;\n}\n\nconst ALLOWED_ELEMENT_ROLES = [\n  { name: 'nav', role: 'navigation' },\n  { name: 'form', role: 'search' },\n  { name: 'ol', role: 'list' },\n  { name: 'ul', role: 'list' },\n  { name: 'a', role: 'link' },\n  { name: 'input', role: 'combobox' },\n];\n\n// Per HTML-AAM, <select> maps to \"combobox\" only when neither `multiple` nor\n// `size > 1` is set; otherwise it maps to \"listbox\". Mirrors jsx-a11y's\n// src/util/implicitRoles/select.js.\n//\n// Returns 'combobox' / 'listbox' for static cases, or 'unknown' when a\n// dynamic `multiple` or `size` value blocks a decision. Callers should skip\n// flagging on 'unknown' to avoid false positives.\nfunction getSelectImplicitRole(node) {\n  const attrs = node.attributes || [];\n  const multipleAttr = attrs.find((a) => a.name === 'multiple');\n  if (multipleAttr) {\n    // Valueless `multiple` or static string value — statically present.\n    if (!multipleAttr.value || multipleAttr.value.type === 'GlimmerTextNode') {\n      return 'listbox';\n    }\n    // Dynamic `multiple={{...}}` — Ember omits bound boolean attributes at\n    // runtime when the value is falsy, so we can't tell statically whether\n    // the implicit role is combobox or listbox.\n    return 'unknown';\n  }\n  const sizeAttr = attrs.find((a) => a.name === 'size');\n  if (sizeAttr) {\n    // Valueless `size` (e.g. `<select size>`) — per HTML boolean-attr\n    // semantics the attribute value is an empty string, which Number()\n    // parses as 0. Per HTML's default size (>1 → listbox), 0 leaves the\n    // implicit role as combobox. Treat the same as the static-0 case.\n    if (!sizeAttr.value) {\n      return 'combobox';\n    }\n    if (sizeAttr.value.type !== 'GlimmerTextNode') {\n      // Dynamic `size={{...}}` / concat — can't tell whether the runtime\n      // value is >1 or not, so bail out instead of risking a false positive.\n      return 'unknown';\n    }\n    const sizeValue = Number(sizeAttr.value.chars);\n    if (Number.isFinite(sizeValue) && sizeValue > 1) {\n      return 'listbox';\n    }\n  }\n  return 'combobox';\n}\n\n// Mapping of roles to their corresponding HTML elements\n// From https://www.w3.org/TR/html-aria/\nconst ROLE_TO_ELEMENTS = {\n  article: ['article'],\n  banner: ['header'],\n  button: ['button'],\n  cell: ['td'],\n  checkbox: ['input'],\n  // <select> is a combobox by default per HTML-AAM (section 4). When\n  // `multiple` is present or `size > 1`, it maps to \"listbox\" instead;\n  // that case is handled at the call site via getSelectImplicitRole.\n  combobox: ['select'],\n  columnheader: ['th'],\n  complementary: ['aside'],\n  contentinfo: ['footer'],\n  definition: ['dd'],\n  dialog: ['dialog'],\n  document: ['body'],\n  figure: ['figure'],\n  form: ['form'],\n  grid: ['table'],\n  gridcell: ['td'],\n  group: ['details', 'fieldset', 'optgroup'],\n  heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],\n  img: ['img'],\n  link: ['a'],\n  list: ['ol', 'ul'],\n  listbox: ['select'],\n  listitem: ['li'],\n  main: ['main'],\n  navigation: ['nav'],\n  option: ['option'],\n  radio: ['input'],\n  // `region` is intentionally NOT mapped to `<section>` here. `<section>`\n  // only gets the `region` landmark role when it has an accessible name\n  // (aria-label / aria-labelledby / title); without one it has role\n  // `generic`. A static linter cannot verify accessible-name presence.\n  // Spec: https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/HTML5.html\n  // See #2694 where the same reasoning was applied to template-no-nested-landmark.\n  row: ['tr'],\n  rowgroup: ['tbody', 'tfoot', 'thead'],\n  rowheader: ['th'],\n  search: ['search'],\n  searchbox: ['input'],\n  separator: ['hr'],\n  slider: ['input'],\n  spinbutton: ['input'],\n  status: ['output'],\n  table: ['table'],\n  term: ['dfn', 'dt'],\n  textbox: ['input', 'textarea'],\n};\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow redundant role attributes',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-redundant-role.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          checkAllHTMLElements: {\n            type: 'boolean',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-redundant-role.js',\n      docs: 'docs/rule/no-redundant-role.md',\n      tests: 'test/unit/rules/no-redundant-role-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseConfig(context.options[0]);\n\n    return {\n      GlimmerElementNode(node) {\n        const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n\n        if (!roleAttr) {\n          return;\n        }\n\n        let roleValue;\n        if (roleAttr.value && roleAttr.value.type === 'GlimmerTextNode') {\n          // ARIA role tokens are compared ASCII-case-insensitively, and the\n          // attribute is a space-separated fallback list. Per WAI-ARIA §4.1,\n          // UAs walk tokens for the first role they RECOGNISE — unknown\n          // leading tokens are skipped, subsequent tokens are author-provided\n          // fallbacks. `role=\"xxyxyz button\"` resolves to `button`;\n          // `role=\"tab button\"` resolves to `tab` (recognised first, even\n          // though no implicit mapping — this rule then has nothing to flag).\n          const tokens = (roleAttr.value.chars || '').trim().toLowerCase().split(/\\s+/u);\n          const firstRecognised = tokens.find((t) => t && roles.has(t));\n          if (!firstRecognised) {\n            return;\n          }\n          roleValue = firstRecognised;\n        } else {\n          // Skip dynamic role values\n          return;\n        }\n\n        const isLandmarkRole = LANDMARK_ROLES.has(roleValue);\n        if (!config.checkAllHTMLElements && !isLandmarkRole) {\n          return;\n        }\n\n        const elementsWithRole = ROLE_TO_ELEMENTS[roleValue];\n        if (!elementsWithRole) {\n          // Role is recognised by ARIA but has no implicit-element mapping\n          // in this table — nothing can be redundant.\n          return;\n        }\n\n        // <select>'s implicit role depends on attributes (HTML-AAM):\n        //   - default (no `multiple`, `size` absent or <= 1) → \"combobox\"\n        //   - `multiple` or `size` > 1                        → \"listbox\"\n        // A role attribute is only redundant when it matches the element's\n        // computed implicit role. Guard both combobox and listbox against\n        // the opposite configuration, and bail when `size` is dynamic\n        // ('unknown') rather than risk a false positive.\n        if (node.tag === 'select' && (roleValue === 'combobox' || roleValue === 'listbox')) {\n          const implicit = getSelectImplicitRole(node);\n          if (implicit !== roleValue) {\n            return;\n          }\n        }\n\n        const isRedundant =\n          elementsWithRole.includes(node.tag) &&\n          !ALLOWED_ELEMENT_ROLES.some((e) => e.name === node.tag && e.role === roleValue);\n\n        if (isRedundant) {\n          const errorMessage = isLandmarkRole\n            ? createErrorMessageLandmarkElement(node.tag, roleValue)\n            : createErrorMessageAnyElement(node.tag, roleValue);\n\n          context.report({\n            node,\n            message: errorMessage,\n            fix(fixer) {\n              const sourceCode = context.sourceCode;\n              const elementText = sourceCode.getText(node);\n              const roleAttrText = sourceCode.getText(roleAttr);\n\n              // Find the role attribute in the element text and remove it along with preceding space\n              const roleAttrPattern = new RegExp(\n                `\\\\s+${roleAttrText.replaceAll(/[$()*+.?[\\\\\\]^{|}]/g, '\\\\$&')}`\n              );\n              const fixedText = elementText.replace(roleAttrPattern, '');\n\n              return fixer.replaceText(node, fixedText);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-restricted-invocations.js",
    "content": "/* eslint-disable unicorn/consistent-function-scoping, unicorn/prefer-switch, curly */\nconst COMPONENT_HELPER_NAME = 'component';\n\nfunction dasherize(str) {\n  return str\n    .split('::')\n    .map((segment) =>\n      segment\n        .replaceAll(/([A-Z])/g, '-$1')\n        .toLowerCase()\n        .replace(/^-/, '')\n    )\n    .join('/');\n}\n\nfunction parseConfig(config) {\n  // If config is not provided, disable the rule\n  if (config === false || config === undefined) {\n    return false;\n  }\n\n  // If it's true, use empty array\n  if (config === true) {\n    return [];\n  }\n\n  // If it's an array, validate it\n  if (Array.isArray(config)) {\n    return config;\n  }\n\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow certain components, helpers or modifiers from being used',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-restricted-invocations.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        oneOf: [\n          {\n            type: 'array',\n            items: {\n              oneOf: [\n                {\n                  type: 'string',\n                },\n                {\n                  type: 'object',\n                  properties: {\n                    names: {\n                      type: 'array',\n                      items: {\n                        type: 'string',\n                      },\n                    },\n                    message: {\n                      type: 'string',\n                    },\n                  },\n                  required: ['names', 'message'],\n                  additionalProperties: false,\n                },\n              ],\n            },\n          },\n        ],\n      },\n    ],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-restricted-invocations.js',\n      docs: 'docs/rule/no-restricted-invocations.md',\n      tests: 'test/unit/rules/no-restricted-invocations-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseConfig(context.options[0]);\n\n    if (config === false) {\n      return {};\n    }\n\n    const sourceCode = context.sourceCode;\n\n    // Track block params in a scope stack so yielded names are not flagged.\n    const blockParamScopes = [];\n\n    function pushBlockParams(params) {\n      blockParamScopes.push(new Set(params || []));\n    }\n\n    function popBlockParams() {\n      blockParamScopes.pop();\n    }\n\n    function isBlockParam(name) {\n      for (const scope of blockParamScopes) {\n        if (scope.has(name)) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    /**\n     * In gjs/gts, check whether a name resolves to a JS-scope variable\n     * (import, const, let, function param, etc.). If it does, it's a local\n     * binding and should be exempt from restriction checks — same as block params.\n     */\n    function isJsScopeVariable(node) {\n      if (!sourceCode) return false;\n\n      try {\n        if (node.type === 'GlimmerElementNode') {\n          // Element nodes use parts[0] for scope lookup, and need parent scope\n          if (!node.parts || !node.parts[0]) return false;\n          const scope = sourceCode.getScope(node.parent);\n          const ref = scope.references.find((r) => r.identifier === node.parts[0]);\n          // Only exempt if the reference actually resolves to a JS variable definition\n          return (\n            ref !== null && ref !== undefined && ref.resolved !== null && ref.resolved !== undefined\n          );\n        }\n\n        // For mustache/block/sub/modifier statements, check the path's head\n        if (node.path && node.path.head) {\n          const scope = sourceCode.getScope(node);\n          const ref = scope.references.find((r) => r.identifier === node.path.head);\n          return (\n            ref !== null && ref !== undefined && ref.resolved !== null && ref.resolved !== undefined\n          );\n        }\n      } catch {\n        // sourceCode.getScope may not be available in .hbs-only mode; ignore.\n      }\n\n      return false;\n    }\n\n    function isRestricted(name) {\n      for (const item of config) {\n        if (typeof item === 'string') {\n          if (item === name) {\n            return { restricted: true, message: null };\n          }\n        } else if (item.names && item.names.includes(name)) {\n          return { restricted: true, message: item.message };\n        }\n      }\n      return { restricted: false };\n    }\n\n    function getComponentOrHelperName(node) {\n      if (node.type === 'GlimmerElementNode') {\n        // Convert angle-bracket names to kebab-case\n        return dasherize(node.tag);\n      }\n\n      if (node.type === 'GlimmerMustacheStatement' || node.type === 'GlimmerBlockStatement') {\n        // Check if it's the component helper\n        if (node.path.original === COMPONENT_HELPER_NAME && node.params && node.params[0]) {\n          // component helper with first param\n          if (node.params[0].type === 'GlimmerStringLiteral') {\n            return node.params[0].value;\n          }\n        }\n        return node.path.original;\n      }\n\n      if (node.type === 'GlimmerModifierStatement') {\n        return node.path.original;\n      }\n\n      if (node.type === 'GlimmerSubExpression') {\n        if (node.path.original === COMPONENT_HELPER_NAME && node.params && node.params[0]) {\n          if (node.params[0].type === 'GlimmerStringLiteral') {\n            return node.params[0].value;\n          }\n        }\n        return node.path.original;\n      }\n\n      return null;\n    }\n\n    function getNodeName(node) {\n      switch (node.type) {\n        case 'GlimmerElementNode': {\n          return `<${node.tag} />`;\n        }\n        case 'GlimmerMustacheStatement': {\n          if (\n            node.path.original === COMPONENT_HELPER_NAME &&\n            node.params?.[0]?.type === 'GlimmerStringLiteral'\n          ) {\n            return `{{component \"${node.params[0].value}\"}}`;\n          }\n          return `{{${node.path.original}}}`;\n        }\n        case 'GlimmerBlockStatement': {\n          if (\n            node.path.original === COMPONENT_HELPER_NAME &&\n            node.params?.[0]?.type === 'GlimmerStringLiteral'\n          ) {\n            return `{{#component \"${node.params[0].value}\"}}`;\n          }\n          return `{{#${node.path.original}}}`;\n        }\n        case 'GlimmerModifierStatement': {\n          return `{{${node.path.original}}}`;\n        }\n        case 'GlimmerSubExpression': {\n          if (\n            node.path.original === COMPONENT_HELPER_NAME &&\n            node.params?.[0]?.type === 'GlimmerStringLiteral'\n          ) {\n            return `(component \"${node.params[0].value}\")`;\n          }\n          return `(${node.path.original})`;\n        }\n        // No default\n      }\n      return '';\n    }\n\n    function checkElementModifiers(node) {\n      if (!node.modifiers) {\n        return;\n      }\n      for (const modifier of node.modifiers) {\n        const modName =\n          modifier.path && modifier.path.type === 'GlimmerPathExpression' && modifier.path.original;\n        if (!modName) continue;\n        if (isBlockParam(modName)) continue;\n        if (isJsScopeVariable(modifier)) continue;\n\n        const modResult = isRestricted(modName);\n        if (modResult.restricted) {\n          context.report({\n            node: modifier,\n            message:\n              modResult.message ||\n              `Cannot use disallowed helper, component or modifier '{{${modName}}}'`,\n          });\n        }\n      }\n    }\n\n    function trackBlockParams(node) {\n      if (node.blockParams && node.blockParams.length > 0) {\n        pushBlockParams(node.blockParams);\n      }\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        // For element nodes, check the raw tag against block params before dasherizing.\n        if (node.tag && isBlockParam(node.tag)) {\n          trackBlockParams(node);\n          return;\n        }\n\n        // In gjs/gts, skip if the tag resolves to a JS-scope variable (import, const, etc.)\n        if (isJsScopeVariable(node)) {\n          trackBlockParams(node);\n          return;\n        }\n\n        const name = getComponentOrHelperName(node);\n        if (name && !isBlockParam(name)) {\n          const result = isRestricted(name);\n          if (result.restricted) {\n            context.report({\n              node,\n              message:\n                result.message ||\n                `Cannot use disallowed helper, component or modifier '${getNodeName(node)}'`,\n            });\n          }\n        }\n\n        trackBlockParams(node);\n        checkElementModifiers(node);\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          popBlockParams();\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        const name = getComponentOrHelperName(node);\n        if (!name) {\n          return;\n        }\n        if (isBlockParam(name)) {\n          return;\n        }\n        if (isJsScopeVariable(node)) {\n          return;\n        }\n\n        const result = isRestricted(name);\n        if (result.restricted) {\n          context.report({\n            node,\n            message:\n              result.message ||\n              `Cannot use disallowed helper, component or modifier '${getNodeName(node)}'`,\n          });\n        }\n      },\n\n      GlimmerBlockStatement(node) {\n        const name = getComponentOrHelperName(node);\n        if (name && !isBlockParam(name) && !isJsScopeVariable(node)) {\n          const result = isRestricted(name);\n          if (result.restricted) {\n            context.report({\n              node,\n              message:\n                result.message ||\n                `Cannot use disallowed helper, component or modifier '${getNodeName(node)}'`,\n            });\n          }\n        }\n\n        // Track block params (e.g. {{#each items as |item|}}).\n        if (node.program && node.program.blockParams) {\n          pushBlockParams(node.program.blockParams);\n        }\n      },\n\n      'GlimmerBlockStatement:exit'(node) {\n        if (node.program && node.program.blockParams) {\n          popBlockParams();\n        }\n      },\n\n      GlimmerModifierStatement(node) {\n        const name = getComponentOrHelperName(node);\n        if (!name) {\n          return;\n        }\n        if (isBlockParam(name)) {\n          return;\n        }\n        if (isJsScopeVariable(node)) {\n          return;\n        }\n\n        const result = isRestricted(name);\n        if (result.restricted) {\n          context.report({\n            node,\n            message:\n              result.message || `Cannot use disallowed helper, component or modifier '{{${name}}}'`,\n          });\n        }\n      },\n\n      GlimmerSubExpression(node) {\n        const name = getComponentOrHelperName(node);\n        if (!name) {\n          return;\n        }\n        if (isBlockParam(name)) {\n          return;\n        }\n        if (isJsScopeVariable(node)) {\n          return;\n        }\n\n        const result = isRestricted(name);\n        if (result.restricted) {\n          context.report({\n            node,\n            message:\n              result.message ||\n              `Cannot use disallowed helper, component or modifier '${getNodeName(node)}'`,\n          });\n        }\n      },\n    };\n  },\n};\n/* eslint-enable unicorn/consistent-function-scoping, unicorn/prefer-switch, curly */\n"
  },
  {
    "path": "lib/rules/template-no-route-action.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of route-action helper',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-route-action.md',\n      templateMode: 'loose',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unexpected:\n        'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-route-action.js',\n      docs: 'docs/rule/no-route-action.md',\n      tests: 'test/unit/rules/no-route-action-test.js',\n    },\n  },\n\n  create(context) {\n    function checkForRouteAction(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        node.path.original === 'route-action'\n      ) {\n        context.report({\n          node,\n          messageId: 'unexpected',\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        checkForRouteAction(node);\n      },\n\n      GlimmerSubExpression(node) {\n        checkForRouteAction(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-scope-outside-table-headings.js",
    "content": "const htmlTags = require('html-tags');\n\nconst HTML_ELEMENTS = new Set(htmlTags);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow scope attribute outside th elements',\n      category: 'Possible Errors',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-scope-outside-table-headings.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noScopeOutsideTableHeadings: 'Unexpected scope attribute on <{{tagName}}>. Use only on <th>.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-scope-outside-table-headings.js',\n      docs: 'docs/rule/no-scope-outside-table-headings.md',\n      tests: 'test/unit/rules/no-scope-outside-table-headings-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const tagName = node.tag;\n\n        if (!tagName || !HTML_ELEMENTS.has(tagName)) {\n          return;\n        }\n\n        const hasScopeAttr = node.attributes.some(\n          (attr) => attr.type === 'GlimmerAttrNode' && attr.name === 'scope'\n        );\n\n        if (hasScopeAttr && tagName !== 'th') {\n          context.report({\n            node,\n            messageId: 'noScopeOutsideTableHeadings',\n            data: { tagName },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-shadowed-elements.js",
    "content": "// Mirror upstream ember-template-lint's inverse-of-isAngleBracketComponent logic.\n// A tag is treated as an HTML element only when it:\n//   - does NOT contain ':' (named blocks like <:slot>)\n//   - does NOT contain '.' (path/namespaced invocations like <foo.bar>)\n//   - does NOT start with '@' (argument invocations like <@foo>)\n//   - has NO uppercase letters (component invocations like <MyThing>)\n//   - does NOT contain '-' (HTML custom elements like <my-element>)\n// Everything else is a component / custom-element / slot — not a plain HTML element.\nfunction isHtmlElement(tagName) {\n  if (!tagName) {\n    return false;\n  }\n  if (tagName.startsWith('@')) {\n    return false;\n  }\n  if (tagName.includes(':')) {\n    return false;\n  }\n  if (tagName.includes('.')) {\n    return false;\n  }\n  if (tagName.includes('-')) {\n    return false;\n  }\n  if (tagName !== tagName.toLowerCase()) {\n    return false;\n  }\n  return true;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow ambiguity with block param names shadowing HTML elements',\n      category: 'Possible Errors',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-shadowed-elements.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      shadowed: 'Component name \"{{name}}\" shadows HTML element <{{name}}>. Use a different name.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-shadowed-elements.js',\n      docs: 'docs/rule/no-shadowed-elements.md',\n      tests: 'test/unit/rules/no-shadowed-elements-test.js',\n    },\n  },\n\n  create(context) {\n    const blockParamScope = [];\n\n    function pushScope(params) {\n      blockParamScope.push(new Set(params || []));\n    }\n\n    function popScope() {\n      blockParamScope.pop();\n    }\n\n    function isLocal(name) {\n      for (const scope of blockParamScope) {\n        if (scope.has(name)) {\n          return true;\n        }\n      }\n      return false;\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        if (node.program && node.program.blockParams) {\n          pushScope(node.program.blockParams);\n        }\n      },\n      'GlimmerBlockStatement:exit'(node) {\n        if (node.program && node.program.blockParams) {\n          popScope();\n        }\n      },\n\n      GlimmerElementNode(node) {\n        // Push block params for elements with 'as |...|' syntax\n        if (node.blockParams && node.blockParams.length > 0) {\n          pushScope(node.blockParams);\n        }\n\n        const tag = node.tag;\n        if (!tag) {\n          return;\n        }\n\n        // Mirror upstream: if the tag is an angle-bracket-component (i.e.\n        // not a plain HTML element — contains '.', is PascalCase, has a\n        // hyphen, etc.) it cannot be a shadow of a native HTML element.\n        // Only a lowercase / simple tag that is a local block param is\n        // considered shadowed. This also covers tags not in any static\n        // html-tags list (upstream does not restrict to a known set).\n        if (!isHtmlElement(tag)) {\n          return;\n        }\n\n        if (isLocal(tag)) {\n          context.report({\n            node,\n            messageId: 'shadowed',\n            data: { name: tag },\n          });\n        }\n      },\n\n      'GlimmerElementNode:exit'(node) {\n        if (node.blockParams && node.blockParams.length > 0) {\n          popScope();\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-splattributes-with-class.js",
    "content": "const ERROR_MESSAGE =\n  'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow splattributes with class attribute',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-splattributes-with-class.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-splattributes-with-class.js',\n      docs: 'docs/rule/no-splattributes-with-class.md',\n      tests: 'test/unit/rules/no-splattributes-with-class-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const hasSplattributes = node.attributes.some((attr) => attr.name === '...attributes');\n        const classAttribute = node.attributes.find((attr) => attr.name === 'class');\n\n        if (hasSplattributes && classAttribute) {\n          context.report({\n            node: classAttribute,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-this-in-template-only-components.js",
    "content": "const { existsSync } = require('node:fs');\nconst path = require('node:path');\n\nconst VALID_COMPONENT_EXTENSIONS = ['.js', '.ts'];\nconst GLIMMER_SCRIPT_EXTENSIONS = ['.gjs', '.gts'];\n\nfunction componentClassExists(pathWithoutExtension) {\n  return VALID_COMPONENT_EXTENSIONS.some((ext) => existsSync(pathWithoutExtension + ext));\n}\n\n// Port of ember-template-lint's no-this-in-template-only-components#isTemplateOnlyComponent.\n// Returns true when the template has no backing component class file on disk.\nfunction isTemplateOnlyComponent(templateFilePath) {\n  // .gjs/.gts files always have JS context, so they cannot be template-only.\n  if (GLIMMER_SCRIPT_EXTENSIONS.some((ext) => templateFilePath.endsWith(ext))) {\n    return false;\n  }\n\n  const allSegments = path.normalize(templateFilePath).split(path.sep);\n\n  const appIndex = allSegments.findIndex((seg) => seg === 'app' || seg === 'addon');\n  if (appIndex === -1) {\n    // No app/addon directory found — cannot determine layout, don't flag.\n    return false;\n  }\n  // Preserve everything before `app`/`addon` as a prefix so we can rebuild\n  // absolute class file paths (upstream uses relative paths, which only\n  // works when the cwd happens to contain `app/`).\n  const prefix = allSegments.slice(0, appIndex).join(path.sep);\n  const segments = allSegments.slice(appIndex);\n\n  if (segments[1] === 'templates') {\n    if (segments[2] === 'components') {\n      // Classic structure: app/templates/components/foo.hbs => app/components/foo.{js,ts}\n      const moduleName = path.basename(templateFilePath, '.hbs');\n      const classFilePath = path.join(\n        prefix,\n        segments[0],\n        'components',\n        ...segments.slice(3, -1),\n        moduleName\n      );\n      return !componentClassExists(classFilePath);\n    }\n    // Route template — always has a backing controller/route context.\n    return false;\n  }\n\n  if (segments[1] === 'components') {\n    // Co-located structure: app/components/foo.hbs => app/components/foo.{js,ts}\n    const moduleName = path.basename(templateFilePath, '.hbs');\n    const classFilePath = path.join(path.dirname(templateFilePath), moduleName);\n    return !componentClassExists(classFilePath);\n  }\n\n  // Unknown layout (e.g. pods) — assume template-only.\n  return true;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow this in template-only components',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-this-in-template-only-components.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noThis:\n        \"Usage of 'this' in path '{{original}}' is not allowed in a template-only component. Use '{{fixed}}' if it is a named argument.\",\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-this-in-template-only-components.js',\n      docs: 'docs/rule/no-this-in-template-only-components.md',\n      tests: 'test/unit/rules/no-this-in-template-only-components-test.js',\n    },\n  },\n  create(context) {\n    // Properties that should not be auto-fixed (built-in component properties)\n    const BUILTIN_PROPERTIES = new Set([\n      'action',\n      'element',\n      'parentView',\n      'attrs',\n      'elementId',\n      'tagName',\n      'ariaRole',\n      'class',\n      'classNames',\n      'classNameBindings',\n      'attributeBindings',\n      'isVisible',\n      'isDestroying',\n      'isDestroyed',\n    ]);\n\n    const filename = context.filename;\n    const isHbs = filename.endsWith('.hbs');\n\n    // For .hbs files, check the filesystem for a companion class file.\n    // If one exists, this is NOT a template-only component — skip entirely.\n    if (isHbs && !isTemplateOnlyComponent(filename)) {\n      return {};\n    }\n\n    return {\n      GlimmerPathExpression(node) {\n        // For .gjs/.gts files: walk ancestors to check if <template> is inside a class body.\n        // If so, the component has a backing class — skip.\n        // .hbs files are already handled above via the filesystem check.\n        if (!isHbs) {\n          const sourceCode = context.sourceCode;\n          const ancestors = sourceCode.getAncestors\n            ? sourceCode.getAncestors(node)\n            : context.getAncestors();\n          const isInsideClass = ancestors.some(\n            (ancestor) => ancestor.type === 'ClassBody' || ancestor.type === 'ClassDeclaration'\n          );\n          if (isInsideClass) {\n            return;\n          }\n        }\n\n        // Check for this.* usage\n        if (node.head?.type === 'ThisHead' && node.tail?.length > 0) {\n          const original = node.original;\n          const firstPart = node.tail[0];\n          const fixed = `@${node.tail.join('.')}`;\n          const canFix = !BUILTIN_PROPERTIES.has(firstPart);\n\n          context.report({\n            node,\n            messageId: 'noThis',\n            data: { original, fixed },\n            fix: canFix ? (fixer) => fixer.replaceText(node, fixed) : undefined,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-trailing-spaces.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow trailing whitespace at the end of lines in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-trailing-spaces.md',\n      templateMode: 'both',\n    },\n    fixable: 'whitespace',\n    schema: [],\n    messages: {\n      unexpected: 'Trailing whitespace detected.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-trailing-spaces.js',\n      docs: 'docs/rule/no-trailing-spaces.md',\n      tests: 'test/unit/rules/no-trailing-spaces-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const glimmerTemplateRanges = [];\n\n    return {\n      GlimmerTemplate(node) {\n        glimmerTemplateRanges.push(node.range);\n      },\n\n      'Program:exit'() {\n        const text = sourceCode.getText();\n        const lines = text.split('\\n');\n        let lineOffset = 0;\n\n        for (const [index, line] of lines.entries()) {\n          if (line.endsWith(' ') || line.endsWith('\\t')) {\n            const trimmedLength = line.trimEnd().length;\n            const trailingStart = lineOffset + trimmedLength;\n            const lineEnd = lineOffset + line.length;\n\n            const isInTemplate = glimmerTemplateRanges.some(\n              ([start, end]) => trailingStart >= start && lineEnd <= end\n            );\n\n            if (isInTemplate) {\n              context.report({\n                loc: {\n                  start: { line: index + 1, column: trimmedLength },\n                  end: { line: index + 1, column: line.length },\n                },\n                messageId: 'unexpected',\n                fix(fixer) {\n                  return fixer.removeRange([trailingStart, lineEnd]);\n                },\n              });\n            }\n          }\n\n          lineOffset += line.length + 1; // +1 for the \\n\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-triple-curlies.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow usage of triple curly brackets (unescaped variables)',\n      category: 'Security',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-triple-curlies.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: { unsafe: 'Usage of triple curly brackets is unsafe' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-triple-curlies.js',\n      docs: 'docs/rule/no-triple-curlies.md',\n      tests: 'test/unit/rules/no-triple-curlies-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerMustacheStatement(node) {\n        if (node.trusting === true) {\n          context.report({\n            node,\n            messageId: 'unsafe',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unavailable-this.js",
    "content": "function isInsideClassOrFunction(node) {\n  let current = node.parent;\n  while (current) {\n    if (\n      current.type === 'ClassBody' ||\n      current.type === 'FunctionExpression' ||\n      current.type === 'FunctionDeclaration'\n    ) {\n      return true;\n    }\n    current = current.parent;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow `this` in templates that are not inside a class or function',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unavailable-this.md',\n      templateMode: 'strict',\n    },\n    schema: [],\n    messages: {\n      noUnavailableThis:\n        '`this` is not available in a template that is not inside a class or function.',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerPathExpression(node) {\n        if (\n          (node.original === 'this' || node.original?.startsWith('this.')) &&\n          !isInsideClassOrFunction(node)\n        ) {\n          context.report({\n            node,\n            messageId: 'noUnavailableThis',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unbalanced-curlies.js",
    "content": "const hbsParser = require('ember-eslint-parser/hbs');\n\nconst SUSPECT_CHARS = '}}';\nconst reLines = /(.*?(?:\\r\\n?|\\n|$))/gm;\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow unbalanced mustache curlies',\n      category: 'Possible Errors',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unbalanced-curlies.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noUnbalancedCurlies: 'Unbalanced curlies detected',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unbalanced-curlies.js',\n      docs: 'docs/rule/no-unbalanced-curlies.md',\n      tests: 'test/unit/rules/no-unbalanced-curlies-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerTextNode(node) {\n        const chars = node.chars;\n\n        if (!chars.includes(SUSPECT_CHARS)) {\n          return;\n        }\n\n        let isMustache = false;\n\n        try {\n          const ast = hbsParser.parseForESLint(chars).ast;\n          const body = ast.body?.[0]?.body ?? ast.body ?? [];\n          isMustache = body.length > 0 && body[0].type === 'GlimmerMustacheStatement';\n        } catch {\n          // Not a valid standalone mustache; continue checking for stray closing curlies.\n        }\n\n        if (isMustache) {\n          return;\n        }\n\n        let lineNum = node.loc.start.line;\n        let colNum = node.loc.start.column;\n        const source = sourceCode.getText(node);\n        const lines = chars.match(reLines) || [];\n\n        for (const line of lines) {\n          const suspectIndex = line.indexOf(SUSPECT_CHARS);\n\n          if (suspectIndex !== -1) {\n            const length = line.slice(suspectIndex).startsWith('}}}') ? 3 : 2;\n\n            context.report({\n              node,\n              messageId: 'noUnbalancedCurlies',\n              loc: {\n                start: { line: lineNum, column: colNum + suspectIndex },\n                end: { line: lineNum, column: colNum + suspectIndex + length },\n              },\n              source,\n            });\n          }\n\n          lineNum++;\n          colNum = 1;\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unbound.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow {{unbound}} helper',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unbound.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: { unexpected: 'Unexpected {{unboundHelper}} usage.' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unbound.js',\n      docs: 'docs/rule/no-unbound.md',\n      tests: 'test/unit/rules/no-unbound-test.js',\n    },\n  },\n  create(context) {\n    // `unbound` is an ambient strict-mode keyword (registered in Ember's\n    // STRICT_MODE_KEYWORDS, backed by BUILTIN_KEYWORD_HELPERS.unbound), so\n    // `{{unbound foo}}` works in .gjs/.gts without an import. Flag it\n    // everywhere unless shadowed by a JS binding or template block param —\n    // ember-eslint-parser registers template block params in scope, so a\n    // single getScope walk covers both.\n    const sourceCode = context.sourceCode;\n\n    function isInScope(node, name) {\n      if (!sourceCode) {\n        return false;\n      }\n      try {\n        let scope = sourceCode.getScope(node);\n        while (scope) {\n          if (scope.variables.some((v) => v.name === name)) {\n            return true;\n          }\n          scope = scope.upper;\n        }\n      } catch {\n        // getScope not available in .hbs-only mode\n      }\n      return false;\n    }\n\n    function check(node) {\n      if (\n        node.path?.type === 'GlimmerPathExpression' &&\n        node.path.original === 'unbound' &&\n        !isInScope(node, 'unbound')\n      ) {\n        context.report({\n          node,\n          messageId: 'unexpected',\n          data: { unboundHelper: '{{unbound}}' },\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement: check,\n      GlimmerBlockStatement: check,\n      GlimmerSubExpression: check,\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unknown-arguments-for-builtin-components.js",
    "content": "function deprecateArgument(componentName, argumentName, replacementAttribute) {\n  const msgs = [`Passing the \"${argumentName}\" argument to <${componentName} /> is deprecated.`];\n  if (replacementAttribute) {\n    msgs.push(\n      `Instead, please pass the attribute directly, i.e. \"<${componentName} ${replacementAttribute}={{...}} />\" instead of \"<${componentName} ${argumentName}={{...}} />\".`\n    );\n  }\n  return msgs.join('\\n');\n}\n\nfunction deprecateEvent(componentName, argumentName, replacementAttribute) {\n  const msgs = [`Passing the \"${argumentName}\" argument to <${componentName} /> is deprecated.`];\n  if (replacementAttribute) {\n    msgs.push(\n      `Instead, please use the {{on}} modifier, i.e. \"<${componentName} {{on \"${replacementAttribute}\" ...}} />\" instead of \"<${componentName} ${argumentName}={{...}} />\".`\n    );\n  }\n  return msgs.join('\\n');\n}\n\nconst KnownArguments = {\n  LinkTo: {\n    arguments: [\n      'route',\n      'model',\n      'models',\n      'query',\n      'replace',\n      'disabled',\n      'current-when',\n      'activeClass',\n      'loadingClass',\n      'disabledClass',\n    ],\n    deprecatedArguments: {\n      '@active': '',\n      '@loading': '',\n      '@init': '',\n      '@didRender': '',\n      '@willDestroy': '',\n      '@didReceiveAttrs': '',\n      '@willRender': '',\n      '@didInsertElement': '',\n      '@didUpdateAttrs': '',\n      '@willUpdate': '',\n      '@didUpdate': '',\n      '@willDestroyElement': '',\n      '@willClearRender': '',\n      '@didDestroyElement': '',\n      '@tagName': '',\n      '@id': 'id',\n      '@elementId': 'id',\n      '@ariaRole': 'role',\n      '@class': 'class',\n      '@classNames': 'class',\n      '@classNameBindings': 'class',\n      '@isVisible': 'style',\n      '@rel': 'rel',\n      '@tabindex': 'tabindex',\n      '@target': 'target',\n      '@title': 'title',\n    },\n    deprecatedEvents: {\n      '@click': 'click',\n      '@contextMenu': 'contextmenu',\n      '@doubleClick': 'dblclick',\n      '@drag': 'drag',\n      '@dragEnd': 'dragend',\n      '@dragEnter': 'dragenter',\n      '@dragLeave': 'dragleave',\n      '@dragOver': 'dragover',\n      '@dragStart': 'dragstart',\n      '@drop': 'drop',\n      '@focusIn': 'focusin',\n      '@focusOut': 'focusout',\n      '@input': 'input',\n      '@keyDown': 'keydown',\n      '@keyPress': 'keypress',\n      '@keyUp': 'keyup',\n      '@mouseDown': 'mousedown',\n      '@mouseEnter': 'mouseenter',\n      '@mouseLeave': 'mouseleave',\n      '@mouseMove': 'mousemove',\n      '@mouseUp': 'mouseup',\n      '@submit': 'submit',\n      '@touchCancel': 'touchcancel',\n      '@touchEnd': 'touchend',\n      '@touchMove': 'touchmove',\n      '@touchStart': 'touchstart',\n    },\n    conflicts: [['model', 'models']],\n    required: [['route', 'query', 'model', 'models']],\n  },\n  Input: {\n    arguments: ['type', 'value', 'checked', 'insert-newline', 'enter', 'escape-press'],\n    deprecatedArguments: {\n      '@bubbles': '',\n      '@cancel': '',\n      '@init': '',\n      '@didRender': '',\n      '@willDestroy': '',\n      '@didReceiveAttrs': '',\n      '@willRender': '',\n      '@didInsertElement': '',\n      '@didUpdateAttrs': '',\n      '@willUpdate': '',\n      '@didUpdate': '',\n      '@willDestroyElement': '',\n      '@willClearRender': '',\n      '@didDestroyElement': '',\n      '@id': 'id',\n      '@elementId': 'id',\n      '@ariaRole': 'role',\n      '@class': 'class',\n      '@classNames': 'class',\n      '@classNameBindings': 'class',\n      '@isVisible': 'style',\n      '@accept': 'accept',\n      '@autocapitalize': '',\n      '@autocomplete': 'autocomplete',\n      '@autocorrect': '',\n      '@autofocus': 'autofocus',\n      '@autosave': '',\n      '@dir': 'dir',\n      '@disabled': 'disabled',\n      '@form': 'form',\n      '@formaction': 'formaction',\n      '@formenctype': 'formenctype',\n      '@formmethod': 'formmethod',\n      '@formnovalidate': 'formnovalidate',\n      '@formtarget': 'formtarget',\n      '@height': 'height',\n      '@indeterminate': '',\n      '@inputmode': '',\n      '@lang': 'lang',\n      '@list': 'list',\n      '@max': 'max',\n      '@maxlength': 'maxlength',\n      '@min': 'min',\n      '@minlength': 'minlength',\n      '@multiple': 'multiple',\n      '@name': 'name',\n      '@pattern': 'pattern',\n      '@placeholder': 'placeholder',\n      '@readonly': 'readonly',\n      '@required': 'required',\n      '@selectionDirection': '',\n      '@size': 'size',\n      '@spellcheck': 'spellcheck',\n      '@step': 'step',\n      '@tabindex': 'tabindex',\n      '@title': 'title',\n      '@width': 'width',\n    },\n    conflicts: [['checked', 'value']],\n    deprecatedEvents: {\n      '@change': 'change',\n      '@click': 'click',\n      '@contextMenu': 'contextmenu',\n      '@doubleClick': 'dblclick',\n      '@drag': 'drag',\n      '@dragEnd': 'dragend',\n      '@dragEnter': 'dragenter',\n      '@dragLeave': 'dragleave',\n      '@dragOver': 'dragover',\n      '@dragStart': 'dragstart',\n      '@drop': 'drop',\n      '@input': 'input',\n      '@mouseDown': 'mousedown',\n      '@mouseEnter': 'mouseenter',\n      '@mouseLeave': 'mouseleave',\n      '@mouseMove': 'mousemove',\n      '@mouseUp': 'mouseup',\n      '@submit': 'submit',\n      '@touchCancel': 'touchcancel',\n      '@touchEnd': 'touchend',\n      '@touchMove': 'touchmove',\n      '@touchStart': 'touchstart',\n      '@focus-in': 'focusin',\n      '@focus-out': 'focusout',\n      '@key-down': 'keydown',\n      '@key-press': 'keypress',\n      '@key-up': 'keyup',\n    },\n  },\n  Textarea: {\n    arguments: ['value', 'insert-newline', 'enter', 'escape-press'],\n    deprecatedArguments: {\n      '@init': '',\n      '@didRender': '',\n      '@willDestroy': '',\n      '@didReceiveAttrs': '',\n      '@willRender': '',\n      '@didInsertElement': '',\n      '@didUpdateAttrs': '',\n      '@willUpdate': '',\n      '@didUpdate': '',\n      '@willDestroyElement': '',\n      '@willClearRender': '',\n      '@didDestroyElement': '',\n      '@id': 'id',\n      '@elementId': 'id',\n      '@ariaRole': 'role',\n      '@class': 'class',\n      '@classNames': 'class',\n      '@classNameBindings': 'class',\n      '@isVisible': 'style',\n      '@autocapitalize': '',\n      '@autocomplete': 'autocomplete',\n      '@autocorrect': '',\n      '@autofocus': 'autofocus',\n      '@cols': 'cols',\n      '@dir': 'dir',\n      '@disabled': 'disabled',\n      '@form': 'form',\n      '@lang': 'lang',\n      '@maxlength': 'maxlength',\n      '@minlength': 'minlength',\n      '@name': 'name',\n      '@placeholder': 'placeholder',\n      '@readonly': 'readonly',\n      '@required': 'required',\n      '@rows': 'rows',\n      '@selectionDirection': '',\n      '@selectionEnd': '',\n      '@selectionStart': '',\n      '@spellcheck': 'spellcheck',\n      '@tabindex': 'tabindex',\n      '@title': 'title',\n      '@wrap': 'wrap',\n    },\n    deprecatedEvents: {\n      '@bubbles': '',\n      '@cancel': '',\n      '@click': 'click',\n      '@contextMenu': 'contextmenu',\n      '@doubleClick': 'dblclick',\n      '@drag': 'drag',\n      '@dragEnd': 'dragend',\n      '@dragEnter': 'dragenter',\n      '@dragLeave': 'dragleave',\n      '@dragOver': 'dragover',\n      '@dragStart': 'dragstart',\n      '@drop': 'drop',\n      '@input': 'input',\n      '@mouseDown': 'mousedown',\n      '@mouseEnter': 'mouseenter',\n      '@mouseLeave': 'mouseleave',\n      '@mouseMove': 'mousemove',\n      '@mouseUp': 'mouseup',\n      '@submit': 'submit',\n      '@touchCancel': 'touchcancel',\n      '@touchEnd': 'touchend',\n      '@touchMove': 'touchmove',\n      '@touchStart': 'touchstart',\n      '@focus-in': 'focusin',\n      '@focus-out': 'focusout',\n      '@key-down': 'keydown',\n      '@key-press': 'keypress',\n      '@key-up': 'keyup',\n    },\n  },\n};\n\nfunction removeAtSymbol(txt) {\n  return txt.replace('@', '');\n}\n\nfunction fuzzyMatch(query, candidates) {\n  // Simple fuzzy match without external dependency\n  const q = query.toLowerCase();\n  let bestMatch = null;\n  let bestScore = 0;\n\n  for (const candidate of candidates) {\n    const c = candidate.toLowerCase();\n    // Simple substring/prefix matching\n    if (c === q) {\n      return candidate;\n    }\n    if (c.startsWith(q) || q.startsWith(c)) {\n      const score = Math.min(q.length, c.length) / Math.max(q.length, c.length);\n      if (score > bestScore) {\n        bestScore = score;\n        bestMatch = candidate;\n      }\n    }\n    // Levenshtein-like: count matching chars\n    let matchCount = 0;\n    let qi = 0;\n    for (let ci = 0; ci < c.length && qi < q.length; ci++) {\n      if (c[ci] === q[qi]) {\n        matchCount++;\n        qi++;\n      }\n    }\n    const score = matchCount / Math.max(q.length, c.length);\n    if (score > bestScore && score > 0.4) {\n      bestScore = score;\n      bestMatch = candidate;\n    }\n  }\n  return bestMatch;\n}\n\nfunction getErrorMessage(tagName, argumentName) {\n  const tagMeta = KnownArguments[tagName];\n  const deprecatedArgs = tagMeta.deprecatedArguments || {};\n  const deprecatedEvents = tagMeta.deprecatedEvents || {};\n  const candidates = [\n    ...new Set([\n      ...tagMeta.arguments,\n      ...Object.keys(deprecatedArgs).map(removeAtSymbol),\n      ...Object.keys(deprecatedEvents).map(removeAtSymbol),\n    ]),\n  ];\n  const pureQuery = removeAtSymbol(argumentName);\n  const hasArgumentsMatch = candidates.includes(pureQuery);\n\n  if (!hasArgumentsMatch) {\n    const msg = `\"${argumentName}\" is not a known argument for the <${tagName} /> component.`;\n    const suggestion = fuzzyMatch(pureQuery, candidates);\n    if (suggestion) {\n      return `${msg} Did you mean \"@${suggestion}\"?`;\n    }\n    return msg;\n  }\n\n  if (argumentName in deprecatedArgs) {\n    return deprecateArgument(tagName, argumentName, deprecatedArgs[argumentName]);\n  }\n\n  if (argumentName in deprecatedEvents) {\n    return deprecateEvent(tagName, argumentName, deprecatedEvents[argumentName]);\n  }\n\n  return `\"${argumentName}\" is unknown argument for <${tagName} /> component.`;\n}\n\nfunction checkConflicts(nodeMeta, node, seen, context) {\n  if (!nodeMeta.conflicts) {\n    return;\n  }\n  for (const conflictList of nodeMeta.conflicts) {\n    if (conflictList.every((item) => seen.includes(item))) {\n      for (const argName of conflictList) {\n        const attr = node.attributes.find(({ name }) => `@${argName}` === name);\n        if (attr) {\n          const conflictsWith = conflictList\n            .filter((el) => `@${el}` !== attr.name)\n            .map((el) => `\"@${el}\"`)\n            .join(', ');\n          context.report({\n            node: attr,\n            messageId: 'conflictArgument',\n            data: {\n              message: `\"${attr.name}\" conflicts with ${conflictsWith}, only one should exist.`,\n            },\n          });\n        }\n      }\n    }\n  }\n}\n\nfunction checkRequired(nodeMeta, node, seen, context) {\n  if (!nodeMeta.required) {\n    return;\n  }\n  for (const requiredItems of nodeMeta.required) {\n    const variants = Array.isArray(requiredItems) ? requiredItems : [requiredItems];\n    if (!variants.some((el) => seen.includes(el))) {\n      const argNames = variants.map((el) => `\"@${el}\"`).join(' or ');\n      context.report({\n        node,\n        messageId: 'requiredArgument',\n        data: {\n          message: `Argument${variants.length > 1 ? 's' : ''} ${argNames} is required for <${node.tag} /> component.`,\n        },\n      });\n    }\n  }\n}\n\n// Rename `@argName=value` to `newName=value` — strips the `@` and swaps\n// the identifier. Used when a deprecated argument has a direct HTML\n// attribute replacement (e.g. `@elementId` -> `id`).\nfunction buildRenameFix(attr, newName) {\n  return (fixer) => {\n    const nameStart = attr.range[0];\n    const nameEnd = nameStart + attr.name.length;\n    return fixer.replaceTextRange([nameStart, nameEnd], newName);\n  };\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow unknown arguments for built-in components',\n      category: 'Possible Errors',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unknown-arguments-for-builtin-components.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      unknownArgument: '{{message}}',\n      conflictArgument: '{{message}}',\n      requiredArgument: '{{message}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unknown-arguments-for-builtin-components.js',\n      docs: 'docs/rule/no-unknown-arguments-for-builtin-components.md',\n      tests: 'test/unit/rules/no-unknown-arguments-for-builtin-components-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    // Remove the attribute entirely (including any preceding whitespace that\n    // separates it from the previous token).\n    function buildRemovalFix(attr) {\n      return (fixer) => {\n        const text = sourceCode.getText();\n        const attrStart = attr.range[0];\n        const attrEnd = attr.range[1];\n\n        let removeStart = attrStart;\n        while (removeStart > 0 && /\\s/.test(text[removeStart - 1])) {\n          removeStart--;\n        }\n\n        return fixer.removeRange([removeStart, attrEnd]);\n      };\n    }\n\n    // Migrate `@eventName={{expr}}` to `{{on \"htmlEvent\" expr}}` modifier\n    // (or `{{on \"htmlEvent\" (helper ...params)}}` when the value is a call).\n    // Only safe when the attribute value is a mustache expression.\n    function buildEventMigrationFix(attr, htmlEventName) {\n      return (fixer) => {\n        const valueText = sourceCode.getText(attr.value);\n        // Strip outer `{{` and `}}` to get the expression text.\n        let inner = valueText;\n        if (inner.startsWith('{{') && inner.endsWith('}}')) {\n          inner = inner.slice(2, -2).trim();\n        }\n        // If the value has parameters (e.g. `action this.click`), wrap as\n        // a sub-expression so the modifier receives a single callable.\n        const hasParams =\n          attr.value &&\n          attr.value.type === 'GlimmerMustacheStatement' &&\n          Array.isArray(attr.value.params) &&\n          attr.value.params.length > 0;\n        const expr = hasParams ? `(${inner})` : inner;\n        const modifier = `{{on \"${htmlEventName}\" ${expr}}}`;\n        return fixer.replaceTextRange([attr.range[0], attr.range[1]], modifier);\n      };\n    }\n\n    function buildFix(node, attr) {\n      const tagMeta = KnownArguments[node.tag];\n      if (!tagMeta) {\n        return null;\n      }\n      const deprecatedArgs = tagMeta.deprecatedArguments || {};\n      const deprecatedEvents = tagMeta.deprecatedEvents || {};\n\n      if (attr.name in deprecatedArgs) {\n        const replacement = deprecatedArgs[attr.name];\n        if (replacement) {\n          // Rename to the equivalent HTML attribute.\n          return buildRenameFix(attr, replacement);\n        }\n        // No replacement attribute — just remove the deprecated arg.\n        return buildRemovalFix(attr);\n      }\n\n      if (attr.name in deprecatedEvents) {\n        const replacement = deprecatedEvents[attr.name];\n        if (!replacement) {\n          // No replacement event (e.g. `@bubbles`) — just remove.\n          return buildRemovalFix(attr);\n        }\n        // Only migrate to `{{on}}` when the value is a mustache expression.\n        // Otherwise (string literal, valueless), leave unfixed.\n        if (attr.value && attr.value.type === 'GlimmerMustacheStatement') {\n          return buildEventMigrationFix(attr, replacement);\n        }\n        return null;\n      }\n\n      // Truly unknown argument (typo) — no autofix.\n      return null;\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        if (!node.tag || !node.attributes) {\n          return;\n        }\n\n        const nodeMeta = KnownArguments[node.tag];\n        if (!nodeMeta) {\n          return;\n        }\n\n        // In gjs/gts, if the tag name resolves to a JS-scope variable with a\n        // definition (e.g. an import or local variable), then it shadows the\n        // Ember built-in component — skip validation.\n        if (node.parent && node.parts && node.parts[0]) {\n          const scope = sourceCode.getScope(node.parent);\n          const isShadowed = scope.references.some(\n            (ref) =>\n              ref.identifier === node.parts[0] && ref.resolved && ref.resolved.defs.length > 0\n          );\n          if (isShadowed) {\n            return;\n          }\n        }\n\n        const seen = [];\n        const warns = [];\n\n        for (const attr of node.attributes) {\n          if (attr.type !== 'GlimmerAttrNode' || !attr.name?.startsWith('@')) {\n            continue;\n          }\n\n          const argName = removeAtSymbol(attr.name);\n          if (nodeMeta.arguments.includes(argName)) {\n            seen.push(argName);\n          } else {\n            warns.push(attr);\n          }\n        }\n\n        // Report unknown/deprecated arguments.\n        for (const attr of warns) {\n          const fix = buildFix(node, attr);\n          context.report({\n            node: attr,\n            messageId: 'unknownArgument',\n            data: { message: getErrorMessage(node.tag, attr.name) },\n            fix: fix || null,\n          });\n        }\n\n        checkConflicts(nodeMeta, node, seen, context);\n        checkRequired(nodeMeta, node, seen, context);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unnecessary-component-helper.js",
    "content": "function toPascalCase(name) {\n  return name\n    .split(/[/-]/)\n    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n    .join('');\n}\n\nfunction isValidIdentifier(name) {\n  return /^[$A-Z_a-z][\\w$]*$/.test(name);\n}\n\nfunction isComponentWithStringLiteral(node) {\n  return (\n    node.path &&\n    node.path.type === 'GlimmerPathExpression' &&\n    node.path.original === 'component' &&\n    node.params &&\n    node.params.length > 0 &&\n    node.params[0].type === 'GlimmerStringLiteral' &&\n    !(node.params[0].value || '').includes('@')\n  );\n}\n\nfunction getComponentInvocationText(sourceCode, node, componentName) {\n  const parts = [];\n\n  for (const param of node.params.slice(1)) {\n    parts.push(sourceCode.getText(param));\n  }\n\n  for (const pair of node.hash?.pairs || []) {\n    parts.push(sourceCode.getText(pair));\n  }\n\n  return [componentName, ...parts].join(' ');\n}\n\nfunction getOpenInvocationEnd(node) {\n  if (node.hash?.pairs?.length) {\n    return node.hash.range[1];\n  }\n\n  const lastParam = node.params.at(-1);\n\n  return lastParam ? lastParam.range[1] : node.path.range[1];\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary component helper',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-component-helper.md',\n      templateMode: 'loose',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noUnnecessaryComponent: 'Invoke component directly instead of using `component` helper',\n      noUnnecessaryComponentKebab:\n        'In GJS/GTS, \"{{name}}\" must be imported as a JS binding (e.g. `import {{pascal}} from \"...\"`). ' +\n        'Invoke it directly as `<{{pascal}}>` instead of via the `component` helper. ' +\n        'The ember-codemods angle-brackets-codemod can automate this migration.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unnecessary-component-helper.js',\n      docs: 'docs/rule/no-unnecessary-component-helper.md',\n      tests: 'test/unit/rules/no-unnecessary-component-helper-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n    let inAttribute = 0;\n\n    // In strict mode, a kebab-case / slash component name cannot become a bare\n    // mustache invocation — the result would not be a valid JS binding and would\n    // require an import. Ecosystem tooling (ember-codemods/angle-brackets-codemod)\n    // handles this migration end-to-end including adding the import.\n    function buildReport(node, componentName, fix) {\n      if (isStrictMode && !isValidIdentifier(componentName)) {\n        return {\n          node,\n          messageId: 'noUnnecessaryComponentKebab',\n          data: { name: componentName, pascal: toPascalCase(componentName) },\n        };\n      }\n      const report = { node, messageId: 'noUnnecessaryComponent' };\n      if (!isStrictMode || isValidIdentifier(componentName)) {\n        report.fix = fix;\n      }\n      return report;\n    }\n\n    return {\n      GlimmerAttrNode() {\n        inAttribute++;\n      },\n      'GlimmerAttrNode:exit'() {\n        inAttribute--;\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (inAttribute > 0) {\n          return;\n        }\n        if (!isComponentWithStringLiteral(node)) {\n          return;\n        }\n\n        const componentName = node.params[0].value || node.params[0].original;\n        const invocation = getComponentInvocationText(sourceCode, node, componentName);\n        context.report(\n          buildReport(node, componentName, (fixer) => fixer.replaceText(node, `{{${invocation}}}`))\n        );\n      },\n\n      GlimmerBlockStatement(node) {\n        if (inAttribute > 0) {\n          return;\n        }\n        if (!isComponentWithStringLiteral(node)) {\n          return;\n        }\n\n        const componentName = node.params[0].value || node.params[0].original;\n        const invocation = getComponentInvocationText(sourceCode, node, componentName);\n\n        context.report(\n          buildReport(node, componentName, (fixer) => {\n            const openInvocationEnd = getOpenInvocationEnd(node);\n            const closingPathEnd = node.range[1] - 2;\n            const closingPathStart = closingPathEnd - node.path.original.length;\n\n            return [\n              fixer.replaceTextRange([node.path.range[0], openInvocationEnd], invocation),\n              fixer.replaceTextRange([closingPathStart, closingPathEnd], componentName),\n            ];\n          })\n        );\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unnecessary-concat.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary string concatenation',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-concat.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      unnecessary: 'Unnecessary string concatenation. Use {{inner}} instead of {{outer}}.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unnecessary-concat.js',\n      docs: 'docs/rule/no-unnecessary-concat.md',\n      tests: 'test/unit/rules/no-unnecessary-concat-test.js',\n    },\n  },\n  create(context) {\n    return {\n      GlimmerConcatStatement(node) {\n        if (node.parts?.length === 1) {\n          const sourceCode = context.sourceCode;\n          context.report({\n            node,\n            messageId: 'unnecessary',\n            data: {\n              inner: sourceCode.getText(node.parts[0]),\n              outer: sourceCode.getText(node),\n            },\n            fix(fixer) {\n              return fixer.replaceText(node, sourceCode.getText(node.parts[0]));\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unnecessary-curly-parens.js",
    "content": "function isFixableMustache(node) {\n  // Check if the mustache's \"path\" is actually a SubExpression with params or hash\n  // e.g., {{(helper arg)}} where the path is (helper arg)\n  return (\n    node.path?.type === 'GlimmerSubExpression' &&\n    ((node.path.params && node.path.params.length > 0) ||\n      (node.path.hash?.pairs && node.path.hash.pairs.length > 0))\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary parentheses enclosing statements in curlies',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-curly-parens.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      noUnnecessaryCurlyParens: 'Unnecessary parentheses enclosing statement',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unnecessary-curly-parens.js',\n      docs: 'docs/rule/no-unnecessary-curly-parens.md',\n      tests: 'test/unit/rules/no-unnecessary-curly-parens-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerMustacheStatement(node) {\n        if (isFixableMustache(node)) {\n          const subExpr = node.path;\n          context.report({\n            node,\n            messageId: 'noUnnecessaryCurlyParens',\n            fix(fixer) {\n              // Replace {{(helper params hash)}} with {{helper params hash}}\n              const helperName = subExpr.path?.original || '';\n              let replacement = `{{${helperName}`;\n\n              for (const param of subExpr.params || []) {\n                replacement += ` ${sourceCode.getText(param)}`;\n              }\n              for (const pair of subExpr.hash?.pairs || []) {\n                replacement += ` ${sourceCode.getText(pair)}`;\n              }\n              replacement += '}}';\n\n              return fixer.replaceText(node, replacement);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unnecessary-curly-strings.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unnecessary curly braces in string interpolations',\n      category: 'Style',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-curly-strings.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: { unnecessary: 'Unnecessary curly braces around string' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unnecessary-curly-strings.js',\n      docs: 'docs/rule/no-unnecessary-curly-strings.md',\n      tests: 'test/unit/rules/no-unnecessary-curly-strings-test.js',\n    },\n  },\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerAttrNode(node) {\n        if (\n          node.value?.type === 'GlimmerMustacheStatement' &&\n          node.value.path?.type === 'GlimmerStringLiteral'\n        ) {\n          const strNode = node.value.path;\n          const strValue = strNode.value || strNode.original;\n          context.report({\n            node: node.value,\n            messageId: 'unnecessary',\n            fix(fixer) {\n              const strSource = sourceCode.getText(strNode);\n              const quoteChar = strSource[0] === \"'\" ? \"'\" : '\"';\n              return fixer.replaceText(node.value, `${quoteChar}${strValue}${quoteChar}`);\n            },\n          });\n        }\n      },\n      GlimmerMustacheStatement(node) {\n        if (node.path?.type === 'GlimmerStringLiteral' && node.parent?.type !== 'GlimmerAttrNode') {\n          const strValue = node.path.value || node.path.original;\n          context.report({\n            node,\n            messageId: 'unnecessary',\n            fix(fixer) {\n              return fixer.replaceText(node, strValue);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unsupported-role-attributes.js",
    "content": "const { roles, elementRoles } = require('aria-query');\n\nfunction getStaticAttrValue(node, name) {\n  const attr = node.attributes?.find((a) => a.name === name);\n  if (!attr) {\n    return undefined;\n  }\n  if (!attr.value || attr.value.type !== 'GlimmerTextNode') {\n    // Presence with dynamic value — treat as \"set\" but unknown string.\n    return '';\n  }\n  return attr.value.chars.trim();\n}\n\nfunction nodeSatisfiesAttributeConstraint(node, attrSpec) {\n  const value = getStaticAttrValue(node, attrSpec.name);\n  const isSet = value !== undefined;\n\n  if (attrSpec.constraints?.includes('set')) {\n    return isSet;\n  }\n  if (attrSpec.constraints?.includes('undefined')) {\n    return !isSet;\n  }\n  if (attrSpec.value !== undefined) {\n    // HTML enumerated attribute values are ASCII case-insensitive\n    // (HTML common-microsyntaxes §2.3.3). aria-query's attrSpec.value is\n    // already lowercase, so lowercase the node's value for comparison.\n    return isSet && value.toLowerCase() === attrSpec.value;\n  }\n  // No constraint listed — just require presence.\n  return isSet;\n}\n\nfunction keyMatchesNode(node, key) {\n  if (key.name !== node.tag) {\n    return false;\n  }\n  if (!key.attributes || key.attributes.length === 0) {\n    return true;\n  }\n  return key.attributes.every((attrSpec) => nodeSatisfiesAttributeConstraint(node, attrSpec));\n}\n\n// Pre-index elementRoles by tag name at module load. aria-query's Map is\n// static data; bucketing by tag turns the per-call scan (~80 keys) into a\n// 1–5 key lookup per tag. Benchmarked at ~2.6× speedup on realistic\n// 200k-call workloads; parity verified across representative tag/attr\n// combinations before landing.\nconst ELEMENT_ROLES_KEYS_BY_TAG = buildElementRolesIndex();\n\nfunction buildElementRolesIndex() {\n  const index = new Map();\n  for (const key of elementRoles.keys()) {\n    if (!index.has(key.name)) {\n      index.set(key.name, []);\n    }\n    index.get(key.name).push(key);\n  }\n  return index;\n}\n\nfunction getImplicitRole(node) {\n  // Honor aria-query's attribute constraints when mapping element -> implicit role.\n  // Each elementRoles entry lists attributes that must match (with optional\n  // constraints \"set\" / \"undefined\"); pick the most specific entry whose\n  // attribute spec is fully satisfied by the node.\n  //\n  // Heuristic: \"specificity = attribute-constraint count\". aria-query exports\n  // elementRoles as an unordered Map and does not document how consumers\n  // should resolve multi-match cases; this count-based tiebreak is an\n  // inference from the data shape. It resolves the motivating bugs:\n  //   - <input type=\"text\"> without `list` → textbox, not combobox\n  //     (the combobox entry requires `list=set`, a stricter 2-attr match;\n  //     the textbox entry's 1-attr type=text wins when `list` is absent).\n  //   - <input type=\"password\"> → no role (no elementRoles entry matches).\n  // If aria-query ever publishes a resolution order, switch to that.\n  const keys = ELEMENT_ROLES_KEYS_BY_TAG.get(node.tag);\n  if (!keys) {\n    return undefined;\n  }\n  let bestKey;\n  let bestSpecificity = -1;\n  for (const key of keys) {\n    if (!keyMatchesNode(node, key)) {\n      continue;\n    }\n    const specificity = key.attributes?.length ?? 0;\n    if (specificity > bestSpecificity) {\n      bestKey = key;\n      bestSpecificity = specificity;\n    }\n  }\n  if (!bestKey) {\n    return undefined;\n  }\n  return elementRoles.get(bestKey)[0];\n}\n\nfunction getExplicitRole(node) {\n  const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n  if (roleAttr && roleAttr.value?.type === 'GlimmerTextNode') {\n    return roleAttr.value.chars.trim();\n  }\n  return null;\n}\n\nfunction removeRangeWithAdjacentWhitespace(sourceText, range) {\n  let [start, end] = range;\n\n  if (sourceText[end - 1] === ' ') {\n    return [start, end];\n  }\n\n  if (sourceText[start - 1] === ' ') {\n    start -= 1;\n  } else if (sourceText[end] === ' ') {\n    end += 1;\n  }\n\n  return [start, end];\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow ARIA attributes that are not supported by the element role',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unsupported-role-attributes.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      unsupportedExplicit: 'The attribute {{attribute}} is not supported by the role {{role}}',\n      unsupportedImplicit:\n        'The attribute {{attribute}} is not supported by the element {{element}} with the implicit role of {{role}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unsupported-role-attributes.js',\n      docs: 'docs/rule/no-unsupported-role-attributes.md',\n      tests: 'test/unit/rules/no-unsupported-role-attributes-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    function reportUnsupported(node, invalidNode, attribute, role, element) {\n      const messageId = element ? 'unsupportedImplicit' : 'unsupportedExplicit';\n\n      context.report({\n        node,\n        messageId,\n        data: element ? { attribute, role, element } : { attribute, role },\n        fix(fixer) {\n          const [start, end] = removeRangeWithAdjacentWhitespace(\n            sourceCode.getText(),\n            invalidNode.range\n          );\n          return fixer.removeRange([start, end]);\n        },\n      });\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        let role = getExplicitRole(node);\n        let element;\n\n        if (!role) {\n          element = node.tag;\n          role = getImplicitRole(node);\n        }\n\n        if (!role) {\n          return;\n        }\n\n        const roleDefinition = roles.get(role);\n        if (!roleDefinition) {\n          return;\n        }\n\n        const supportedProps = Object.keys(roleDefinition.props);\n\n        for (const attr of node.attributes || []) {\n          if (attr.type !== 'GlimmerAttrNode' || !attr.name?.startsWith('aria-')) {\n            continue;\n          }\n\n          if (!supportedProps.includes(attr.name)) {\n            reportUnsupported(node, attr, attr.name, role, element);\n          }\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (!node.hash || !node.hash.pairs) {\n          return;\n        }\n\n        const rolePair = node.hash.pairs.find((pair) => pair.key === 'role');\n        if (!rolePair || rolePair.value?.type !== 'GlimmerStringLiteral') {\n          return;\n        }\n\n        const role = rolePair.value.value;\n        if (!role) {\n          return;\n        }\n\n        const roleDefinition = roles.get(role);\n        if (!roleDefinition) {\n          return;\n        }\n\n        const supportedProps = Object.keys(roleDefinition.props);\n        const ariaPairs = node.hash.pairs.filter((pair) => pair.key.startsWith('aria-'));\n\n        for (const pair of ariaPairs) {\n          if (!supportedProps.includes(pair.key)) {\n            reportUnsupported(node, pair, pair.key, role);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-unused-block-params.js",
    "content": "function collectChildNodes(n) {\n  const children = [];\n  if (n.program) {\n    children.push(n.program);\n  }\n  if (n.inverse) {\n    children.push(n.inverse);\n  }\n  if (n.params) {\n    children.push(...n.params);\n  }\n  if (n.hash?.pairs) {\n    children.push(...n.hash.pairs.map((p) => p.value));\n  }\n  if (n.body) {\n    children.push(...n.body);\n  }\n  if (n.path) {\n    children.push(n.path);\n  }\n  if (n.attributes) {\n    children.push(...n.attributes.map((a) => a.value));\n  }\n  if (n.children) {\n    children.push(...n.children);\n  }\n  if (n.modifiers) {\n    children.push(...n.modifiers);\n  }\n  // GlimmerPathExpression also has 'parts', so make sure we're not treating\n  // concat'd path string parts as AST nodes.\n  if (n.type === 'GlimmerConcatStatement' && n.parts) {\n    children.push(...n.parts);\n  }\n  return children;\n}\n\nfunction markParamIfUsed(name, blockParams, usedParams, shadowedParams) {\n  const firstPart = name.split('.')[0];\n  if (blockParams.includes(firstPart) && !shadowedParams.has(firstPart)) {\n    usedParams.add(firstPart);\n  }\n}\n\nfunction isPartialStatement(n) {\n  return (\n    (n.type === 'GlimmerMustacheStatement' || n.type === 'GlimmerBlockStatement') &&\n    n.path?.original === 'partial'\n  );\n}\n\nfunction buildShadowedSet(shadowedParams, innerBlockParams, outerBlockParams) {\n  const newShadowed = new Set(shadowedParams);\n  for (const p of innerBlockParams) {\n    if (outerBlockParams.includes(p)) {\n      newShadowed.add(p);\n    }\n  }\n  return newShadowed;\n}\n\nfunction checkBlockParts(n, blockParams, usedParams, shadowedParams, newShadowed, checkNodeFn) {\n  // Check the path/params of the block statement itself with current scope\n  if (n.path) {\n    checkNodeFn(n.path, shadowedParams);\n  }\n  if (n.params) {\n    for (const param of n.params) {\n      checkNodeFn(param, shadowedParams);\n    }\n  }\n  if (n.hash?.pairs) {\n    for (const pair of n.hash.pairs) {\n      checkNodeFn(pair.value, shadowedParams);\n    }\n  }\n\n  // Check the program body with the updated shadowed set\n  if (n.program) {\n    checkNodeFn(n.program, newShadowed);\n  }\n  if (n.inverse) {\n    checkNodeFn(n.inverse, newShadowed);\n  }\n}\n\n/**\n * Scan child nodes for usage of blockParams, then report the first unused\n * trailing param. Shared by both GlimmerBlockStatement and GlimmerElementNode.\n */\nfunction checkUnusedBlockParams(context, node, blockParams, startNodes) {\n  const usedParams = new Set();\n\n  function checkNode(n, shadowedParams) {\n    if (!n) {\n      return;\n    }\n\n    if (n.type === 'GlimmerPathExpression') {\n      markParamIfUsed(n.original, blockParams, usedParams, shadowedParams);\n    }\n\n    if (n.type === 'GlimmerElementNode') {\n      markParamIfUsed(n.tag, blockParams, usedParams, shadowedParams);\n    }\n\n    if (isPartialStatement(n)) {\n      for (const p of blockParams) {\n        if (!shadowedParams.has(p)) {\n          usedParams.add(p);\n        }\n      }\n    }\n\n    // Nested block with its own blockParams — shadow them\n    if (n.type === 'GlimmerBlockStatement' && n.program?.blockParams?.length > 0) {\n      const newShadowed = buildShadowedSet(shadowedParams, n.program.blockParams, blockParams);\n      checkBlockParts(n, blockParams, usedParams, shadowedParams, newShadowed, checkNode);\n      return;\n    }\n\n    // Nested element with block params (e.g. <Component as |x|>) — shadow them\n    if (n.type === 'GlimmerElementNode' && n.blockParams?.length > 0) {\n      const newShadowed = buildShadowedSet(shadowedParams, n.blockParams, blockParams);\n      if (n.attributes) {\n        for (const attr of n.attributes) {\n          checkNode(attr.value, shadowedParams);\n        }\n      }\n      if (n.children) {\n        for (const child of n.children) {\n          checkNode(child, newShadowed);\n        }\n      }\n      return;\n    }\n\n    for (const child of collectChildNodes(n)) {\n      checkNode(child, shadowedParams);\n    }\n  }\n\n  for (const startNode of startNodes) {\n    checkNode(startNode, new Set());\n  }\n\n  // Find the last index of a used param\n  let lastUsedIndex = -1;\n  for (let i = blockParams.length - 1; i >= 0; i--) {\n    if (usedParams.has(blockParams[i])) {\n      lastUsedIndex = i;\n      break;\n    }\n  }\n\n  // Only report trailing unused params (after the last used one)\n  const unusedTrailing = blockParams.slice(lastUsedIndex + 1);\n  const firstUnusedTrailing = unusedTrailing[0];\n\n  if (firstUnusedTrailing) {\n    context.report({\n      node,\n      messageId: 'unusedBlockParam',\n      data: { param: firstUnusedTrailing },\n    });\n  }\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow unused block parameters in templates',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unused-block-params.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      unusedBlockParam: \"'{{param}}' is defined but never used\",\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-unused-block-params.js',\n      docs: 'docs/rule/no-unused-block-params.md',\n      tests: 'test/unit/rules/no-unused-block-params-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerBlockStatement(node) {\n        const blockParams = node.program?.blockParams || [];\n        if (blockParams.length > 0) {\n          checkUnusedBlockParams(context, node, blockParams, [node.program]);\n        }\n      },\n\n      GlimmerElementNode(node) {\n        const blockParams = node.blockParams || [];\n        if (blockParams.length > 0) {\n          checkUnusedBlockParams(context, node, blockParams, node.children || []);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-valueless-arguments.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nfunction isNamedArgument(attrName) {\n  return attrName.startsWith('@');\n}\n\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow valueless named arguments',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-valueless-arguments.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: { valueless: 'Named arguments should have an explicitly assigned value.' },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-valueless-arguments.js',\n      docs: 'docs/rule/no-valueless-arguments.md',\n      tests: 'test/unit/rules/no-valueless-arguments-test.js',\n    },\n  },\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerAttrNode(node) {\n        if (!isNamedArgument(node.name)) {\n          return;\n        }\n\n        if (!sourceCode.getText(node).includes('=')) {\n          context.report({ node, messageId: 'valueless' });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-whitespace-for-layout.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nconst ERROR_MESSAGE = 'Excess whitespace detected.';\n\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow using whitespace for layout purposes',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-whitespace-for-layout.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      noWhitespaceForLayout: ERROR_MESSAGE,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-whitespace-for-layout.js',\n      docs: 'docs/rule/no-whitespace-for-layout.md',\n      tests: 'test/unit/rules/no-whitespace-for-layout-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerTextNode(node) {\n        // Only flag body text, not attribute values. ember-eslint-parser\n        // emits attribute values as GlimmerTextNode children of a\n        // GlimmerAttrNode; skip those so only element body whitespace is\n        // checked.\n        if (node.parent?.type === 'GlimmerAttrNode') {\n          return;\n        }\n\n        const text = sourceCode.getText(node);\n        if (!text) {\n          return;\n        }\n\n        const lines = text.split('\\n');\n        for (const line of lines) {\n          // Ignore whitespace at the start and end of the line\n          const trimmed = line.trim();\n\n          // Check for two consecutive ` ` or `&nbsp;` in a row\n          if (/(( )|(&nbsp;))(( )|(&nbsp;))/.test(trimmed)) {\n            context.report({\n              node,\n              messageId: 'noWhitespaceForLayout',\n            });\n            return;\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-whitespace-within-word.js",
    "content": "const WHITESPACE_ENTITY_LIST = [\n  '&#32;',\n  '&#160;',\n  '&nbsp;',\n  '&NonBreakingSpace;',\n  '&#8194;',\n  '&ensp;',\n  '&#8195;',\n  '&emsp;',\n  '&#8196;',\n  '&emsp13;',\n  '&#8197;',\n  '&emsp14;',\n  '&#8199;',\n  '&numsp;',\n  '&#8200;',\n  '&puncsp;',\n  '&#8201;',\n  '&thinsp;',\n  '&ThinSpace;',\n  '&#8202;',\n  '&hairsp;',\n  '&VeryThinSpace;',\n  '&ThickSpace;',\n  '&#8203;',\n  '&ZeroWidthSpace;',\n  '&NegativeVeryThinSpace;',\n  '&NegativeThinSpace;',\n  '&NegativeMediumSpace;',\n  '&NegativeThickSpace;',\n  '&#8204;',\n  '&zwnj;',\n  '&#8205;',\n  '&zwj;',\n  '&#8206;',\n  '&lrm;',\n  '&#8207;',\n  '&rlm;',\n  '&#8287;',\n  '&MediumSpace;',\n  '&ThickSpace;',\n  '&#8288;',\n  '&NoBreak;',\n  '&#8289;',\n  '&ApplyFunction;',\n  '&af;',\n  '&#8290;',\n  '&InvisibleTimes;',\n  '&it;',\n  '&#8291;',\n  '&InvisibleComma;',\n  '&ic;',\n];\n\nconst CHARACTER_REGEX = '[a-zA-Z]';\n\n// Build a regex that catches alternating non-whitespace/whitespace characters,\n// for example, 'W e l c o m e'. The pattern requires 5 alternations to avoid\n// false positives: (whitespace)(char)(whitespace)(char)(whitespace)\nconst whitespaceOrEntityRegex = `(?:\\\\s|${WHITESPACE_ENTITY_LIST.map(\n  (entity) => `\\\\${entity}`\n).join('|')})+`;\nconst WHITESPACE_WITHIN_WORD_REGEX = new RegExp(\n  `${whitespaceOrEntityRegex}${CHARACTER_REGEX}${whitespaceOrEntityRegex}${CHARACTER_REGEX}${whitespaceOrEntityRegex}`\n);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'disallow excess whitespace within words (e.g. \"W e l c o m e\")',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-whitespace-within-word.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      excessWhitespace: 'Excess whitespace in layout detected.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-whitespace-within-word.js',\n      docs: 'docs/rule/no-whitespace-within-word.md',\n      tests: 'test/unit/rules/no-whitespace-within-word-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerTextNode(node) {\n        // Skip text inside attributes\n        let parent = node.parent;\n        while (parent) {\n          if (parent.type === 'GlimmerAttrNode') {\n            return;\n          }\n          // Skip text inside <style> elements\n          if (parent.type === 'GlimmerElementNode' && parent.tag === 'style') {\n            return;\n          }\n          parent = parent.parent;\n        }\n\n        const text = sourceCode.getText(node);\n        if (WHITESPACE_WITHIN_WORD_REGEX.test(text)) {\n          context.report({\n            node,\n            messageId: 'excessWhitespace',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-with.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nconst DEPRECATION_URL = 'https://deprecations.emberjs.com/v3.x/#toc_ember-glimmer-with-syntax';\n\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow {{with}} helper',\n      category: 'Deprecations',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-with.md',\n      templateMode: 'loose',\n    },\n    schema: [],\n    messages: {\n      deprecated: `The use of \\`{{withHelper}}\\` has been deprecated. Please see the deprecation guide at ${DEPRECATION_URL}.`,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-with.js',\n      docs: 'docs/rule/no-with.md',\n      tests: 'test/unit/rules/no-with-test.js',\n    },\n  },\n  create(context) {\n    return {\n      GlimmerBlockStatement(node) {\n        if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'with') {\n          context.report({ node, messageId: 'deprecated', data: { withHelper: '{{with}}' } });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-yield-block-params-to-else-inverse.js",
    "content": "const ERROR_MESSAGE = 'Yielding block params to else/inverse block is not allowed';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow yielding block params to else or inverse block',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-yield-block-params-to-else-inverse.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noYieldBlockParamsToElseInverse: ERROR_MESSAGE,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-yield-block-params-to-else-inverse.js',\n      docs: 'docs/rule/no-yield-block-params-to-else-inverse.md',\n      tests: 'test/unit/rules/no-yield-block-params-to-else-inverse-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerMustacheStatement(node) {\n        // Only check yield statements\n        if (node.path.original !== 'yield') {\n          return;\n        }\n\n        // Must have params\n        if (!node.params || node.params.length === 0) {\n          return;\n        }\n\n        // Check if there's a 'to' hash with 'else' or 'inverse' value\n        if (node.hash && node.hash.pairs) {\n          const toPair = node.hash.pairs.find((pair) => pair.key === 'to');\n\n          if (toPair && toPair.value && toPair.value.type === 'GlimmerStringLiteral') {\n            const toValue = toPair.value.value;\n            if (toValue === 'else' || toValue === 'inverse') {\n              context.report({\n                node,\n                messageId: 'noYieldBlockParamsToElseInverse',\n              });\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-yield-only.js",
    "content": "function isEmptyNode(node) {\n  return (\n    node.type === 'GlimmerMustacheCommentStatement' ||\n    node.type === 'GlimmerCommentStatement' ||\n    (node.type === 'GlimmerTextNode' && !node.chars.trim())\n  );\n}\n\nfunction isYieldOnly(node) {\n  return (\n    node.type === 'GlimmerMustacheStatement' &&\n    node.path &&\n    node.path.type === 'GlimmerPathExpression' &&\n    node.path.original === 'yield' &&\n    node.params &&\n    node.params.length === 0\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow components that only yield',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-yield-only.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      noYieldOnly: '{{yieldExpression}}-only templates are not allowed',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-yield-only.js',\n      docs: 'docs/rule/no-yield-only.md',\n      tests: 'test/unit/rules/no-yield-only-test.js',\n    },\n  },\n\n  create(context) {\n    let isOnlyYield = false;\n\n    return {\n      GlimmerTemplate(node) {\n        const templateNodes =\n          node.body[0] &&\n          node.body[0].type === 'GlimmerElementNode' &&\n          node.body[0].tag === 'template'\n            ? node.body[0].children\n            : node.body;\n\n        const nonEmptyNodes = templateNodes.filter((n) => !isEmptyNode(n));\n        if (nonEmptyNodes.length === 1 && isYieldOnly(nonEmptyNodes[0])) {\n          isOnlyYield = true;\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (isOnlyYield) {\n          context.report({\n            node,\n            messageId: 'noYieldOnly',\n            data: { yieldExpression: '{{yield}}' },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-no-yield-to-default.js",
    "content": "const ERROR_MESSAGE = 'A block named \"default\" is not valid';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow yield to default block',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-yield-to-default.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      invalidDefaultBlock: ERROR_MESSAGE,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/no-yield-to-default.js',\n      docs: 'docs/rule/no-yield-to-default.md',\n      tests: 'test/unit/rules/no-yield-to-default-test.js',\n    },\n  },\n\n  create(context) {\n    const BLOCK_HELPERS = new Set(['has-block', 'has-block-params', 'hasBlock', 'hasBlockParams']);\n\n    function checkDefaultBlockHelper(node) {\n      if (\n        node.path &&\n        node.path.type === 'GlimmerPathExpression' &&\n        BLOCK_HELPERS.has(node.path.original) &&\n        node.params &&\n        node.params.length > 0 &&\n        node.params[0].type === 'GlimmerStringLiteral' &&\n        node.params[0].value === 'default'\n      ) {\n        context.report({\n          node: node.params[0],\n          messageId: 'invalidDefaultBlock',\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        if (\n          node.path &&\n          node.path.type === 'GlimmerPathExpression' &&\n          node.path.original === 'yield' &&\n          node.hash &&\n          node.hash.pairs\n        ) {\n          for (const pair of node.hash.pairs) {\n            if (\n              pair.key === 'to' &&\n              pair.value.type === 'GlimmerStringLiteral' &&\n              pair.value.value === 'default'\n            ) {\n              context.report({\n                node: pair,\n                messageId: 'invalidDefaultBlock',\n              });\n            }\n          }\n        }\n        checkDefaultBlockHelper(node);\n      },\n      GlimmerSubExpression(node) {\n        checkDefaultBlockHelper(node);\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-quotes.js",
    "content": "function attrValueHasChar(node, ch) {\n  if (node.type === 'GlimmerTextNode') {\n    return node.chars.includes(ch);\n  }\n  if (node.type === 'GlimmerConcatStatement') {\n    return (node.parts || []).some((n) => n.type === 'GlimmerTextNode' && n.chars.includes(ch));\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce consistent quote style in templates',\n      category: 'Stylistic Issues',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-quotes.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        oneOf: [\n          { enum: ['double', 'single'] },\n          // `false` as the root config disables the rule, matching upstream\n          // ember-template-lint which accepts `[rule, false]` as a valid\n          // disabled state without schema errors.\n          { type: 'boolean', enum: [false] },\n          {\n            type: 'object',\n            required: ['curlies', 'html'],\n            properties: {\n              curlies: { enum: ['double', 'single', false] },\n              html: { enum: ['double', 'single', false] },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      wrongQuotes: '{{message}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/quotes.js',\n      docs: 'docs/rule/quotes.md',\n      tests: 'test/unit/rules/quotes-test.js',\n    },\n  },\n\n  create(context) {\n    const rawOption = context.options[0];\n\n    // Disabled when options omitted or explicitly set to `false`.\n    if (!rawOption) {\n      return {};\n    }\n\n    const config =\n      typeof rawOption === 'string' ? { curlies: rawOption, html: rawOption } : rawOption;\n\n    if (!config.curlies && !config.html) {\n      return {};\n    }\n\n    const chars = { single: \"'\", double: '\"' };\n    const goodChars = {\n      curlies: config.curlies ? chars[config.curlies] : null,\n      html: config.html ? chars[config.html] : null,\n    };\n    const badChars = {\n      curlies: goodChars.curlies\n        ? goodChars.curlies === chars.single\n          ? chars.double\n          : chars.single\n        : null,\n      html: goodChars.html ? (goodChars.html === chars.single ? chars.double : chars.single) : null,\n    };\n\n    let message;\n    if (goodChars.curlies === goodChars.html && goodChars.curlies) {\n      message = `you must use ${config.curlies} quotes in templates`;\n    } else if (!goodChars.curlies || !goodChars.html) {\n      const active = goodChars.curlies || goodChars.html;\n      const activeType = active === chars.single ? 'single' : 'double';\n      const context_ = goodChars.curlies ? 'Handlebars syntax' : 'HTML attributes';\n      message = `you must use ${activeType} quotes in ${context_}`;\n    } else {\n      const doubleCtx =\n        goodChars.curlies === chars.double ? 'Handlebars syntax' : 'HTML attributes';\n      const singleCtx =\n        goodChars.curlies === chars.single ? 'Handlebars syntax' : 'HTML attributes';\n      message = `you must use double quotes for ${doubleCtx} and single quotes for ${singleCtx} in templates`;\n    }\n\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerAttrNode(node) {\n        if (!goodChars.html || !badChars.html) {\n          return;\n        }\n        // Skip valueless attributes\n        if (!node.value || node.isValueless) {\n          return;\n        }\n        const raw = sourceCode.getText(node);\n        // Extract quote char used: attr=\"...\" or attr='...'\n        const eqIndex = raw.indexOf('=');\n        if (eqIndex === -1) {\n          return;\n        }\n        const afterEq = raw[eqIndex + 1];\n        if (afterEq !== badChars.html) {\n          return;\n        }\n        // If the value contains the desired quote char, we can't autofix\n        if (attrValueHasChar(node.value, goodChars.html)) {\n          context.report({ node, messageId: 'wrongQuotes', data: { message } });\n          return;\n        }\n        context.report({\n          node,\n          messageId: 'wrongQuotes',\n          data: { message },\n          fix(fixer) {\n            const nodeText = sourceCode.getText(node);\n            const eqIdx = nodeText.indexOf('=');\n            const valuePart = nodeText.slice(eqIdx + 1);\n            // Replace outer quotes\n            const newValuePart = goodChars.html + valuePart.slice(1, -1) + goodChars.html;\n            const newText = nodeText.slice(0, eqIdx + 1) + newValuePart;\n            return fixer.replaceText(node, newText);\n          },\n        });\n      },\n\n      GlimmerStringLiteral(node) {\n        if (!goodChars.curlies || !badChars.curlies) {\n          return;\n        }\n        const raw = sourceCode.getText(node);\n        if (!raw || raw.length < 2) {\n          return;\n        }\n        const usedQuote = raw[0];\n        if (usedQuote !== badChars.curlies) {\n          return;\n        }\n        // If the value contains the desired quote char, we can't autofix\n        if (node.value && node.value.includes(goodChars.curlies)) {\n          context.report({ node, messageId: 'wrongQuotes', data: { message } });\n          return;\n        }\n        context.report({\n          node,\n          messageId: 'wrongQuotes',\n          data: { message },\n          fix(fixer) {\n            const newText = goodChars.curlies + raw.slice(1, -1) + goodChars.curlies;\n            return fixer.replaceText(node, newText);\n          },\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-aria-activedescendant-tabindex.js",
    "content": "const { dom } = require('aria-query');\n\nconst HTML_TAGS = new Set(dom.keys());\nconst INTERACTIVE_ELEMENTS = new Set(['input', 'button', 'select', 'textarea']);\nconst ERROR_MESSAGE =\n  'A generic element using the aria-activedescendant attribute must have a tabindex';\nconst FIXED_TABINDEX = 'tabindex=\"0\"';\n\nfunction isInteractiveElement(node) {\n  if (INTERACTIVE_ELEMENTS.has(node.tag)) {\n    return true;\n  }\n  // <a> with href is interactive\n  if (node.tag === 'a' && node.attributes?.some((a) => a.name === 'href')) {\n    return true;\n  }\n  return false;\n}\n\nfunction getTabindexNumericValue(tabindexAttr) {\n  if (!tabindexAttr) {\n    return Number.nan;\n  }\n\n  const value = tabindexAttr.value;\n\n  if (value.type === 'GlimmerMustacheStatement' && value.path) {\n    if (\n      ['GlimmerBooleanLiteral', 'GlimmerNumberLiteral', 'GlimmerStringLiteral'].includes(\n        value.path.type\n      )\n    ) {\n      return Number(value.path.value);\n    }\n\n    return Number.nan;\n  }\n\n  if (value.type === 'GlimmerTextNode') {\n    return Number.parseInt(value.chars, 10);\n  }\n\n  return Number.nan;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require non-interactive elements with aria-activedescendant to have tabindex',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-aria-activedescendant-tabindex.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      missingTabindex: ERROR_MESSAGE,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-aria-activedescendant-tabindex.js',\n      docs: 'docs/rule/require-aria-activedescendant-tabindex.md',\n      tests: 'test/unit/rules/require-aria-activedescendant-tabindex-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const hasActiveDescendant = node.attributes?.some(\n          (attr) => attr.name === 'aria-activedescendant'\n        );\n\n        if (!hasActiveDescendant) {\n          return;\n        }\n\n        if (!HTML_TAGS.has(node.tag)) {\n          return;\n        }\n\n        const tabindexAttr = node.attributes?.find(\n          (attr) => attr.name === 'tabindex' || attr.name === 'tabIndex'\n        );\n\n        if (!tabindexAttr && isInteractiveElement(node)) {\n          return;\n        }\n\n        const tabindexValue = getTabindexNumericValue(tabindexAttr);\n\n        if (!Number.isFinite(tabindexValue) || tabindexValue < -1) {\n          context.report({\n            node,\n            messageId: 'missingTabindex',\n            fix(fixer) {\n              if (!tabindexAttr) {\n                const lastAttribute = node.attributes.at(-1);\n\n                if (lastAttribute) {\n                  return fixer.insertTextAfterRange(lastAttribute.range, ` ${FIXED_TABINDEX}`);\n                }\n\n                const insertPos =\n                  node.parts.at(-1)?.range[1] ?? node.range[0] + 1 + node.tag.length;\n                return fixer.insertTextAfterRange([insertPos, insertPos], ` ${FIXED_TABINDEX}`);\n              }\n\n              return fixer.replaceTextRange(tabindexAttr.range, FIXED_TABINDEX);\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-button-type.js",
    "content": "function hasParentForm(node) {\n  let current = node.parent;\n  while (current) {\n    if (current.type === 'GlimmerElementNode' && current.tag === 'form') {\n      return true;\n    }\n    current = current.parent;\n  }\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require button elements to have a valid type attribute',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-button-type.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      missing: 'All `<button>` elements should have a valid `type` attribute',\n      invalid: 'All `<button>` elements should have a valid `type` attribute',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-button-type.js',\n      docs: 'docs/rule/require-button-type.md',\n      tests: 'test/unit/rules/require-button-type-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'button') {\n          return;\n        }\n\n        const typeAttr = node.attributes?.find((attr) => attr.name === 'type');\n\n        if (!typeAttr) {\n          context.report({\n            node,\n            messageId: 'missing',\n            fix(fixer) {\n              // If inside a form, default to \"submit\", otherwise \"button\"\n              const defaultType = hasParentForm(node) ? 'submit' : 'button';\n\n              const text = sourceCode.getText(node);\n\n              // Handle self-closing: <button/> or <button />\n              if (/^<button\\s*\\/>/.test(text)) {\n                return fixer.replaceText(node, `<button type=\"${defaultType}\" />`);\n              }\n\n              // Find the position to insert the attribute\n              const openTag = text.match(/^<button[^>]*/)[0];\n              const insertPos = node.range[0] + openTag.length;\n\n              return fixer.insertTextBeforeRange([insertPos, insertPos], ` type=\"${defaultType}\"`);\n            },\n          });\n          return;\n        }\n\n        // Check if the type value is valid\n        const value = typeAttr.value;\n        if (value && value.type === 'GlimmerTextNode') {\n          const typeValue = value.chars;\n          if (!['button', 'submit', 'reset'].includes(typeValue)) {\n            context.report({\n              node: typeAttr,\n              messageId: 'invalid',\n              fix(fixer) {\n                return fixer.replaceText(typeAttr, 'type=\"button\"');\n              },\n            });\n          }\n        }\n        // Note: Dynamic type values (e.g., type={{this.dynamicType}}) cannot be\n        // validated at lint time, so we don't report them as errors\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-context-role.js",
    "content": "const ROLES_REQUIRING_CONTEXT = {\n  cell: ['row'],\n  listitem: ['group', 'list'],\n  option: ['listbox'],\n  tab: ['tablist'],\n  menuitem: ['group', 'menu', 'menubar'],\n  menuitemcheckbox: ['menu', 'menubar'],\n  menuitemradio: ['group', 'menu', 'menubar'],\n  treeitem: ['group', 'tree'],\n  row: ['grid', 'rowgroup', 'table', 'treegrid'],\n  rowgroup: ['grid', 'table', 'treegrid'],\n  rowheader: ['grid', 'row'],\n  columnheader: ['row'],\n  gridcell: ['row'],\n};\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require ARIA roles to be used in appropriate context',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-context-role.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      missingContext:\n        'Role \"{{role}}\" must be contained in an element with one of these roles: {{requiredRoles}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-context-role.js',\n      docs: 'docs/rule/require-context-role.md',\n      tests: 'test/unit/rules/require-context-role-test.js',\n    },\n  },\n\n  create(context) {\n    const elementStack = [];\n\n    return {\n      GlimmerElementNode(node) {\n        elementStack.push(node);\n\n        const role = getRoleFromNode(node);\n\n        if (role && ROLES_REQUIRING_CONTEXT[role]) {\n          // Skip check if at root level (no parent elements — context may be external)\n          if (elementStack.length > 1) {\n            const parentContext = getParentContext(elementStack);\n            if (parentContext.ariaHidden) {\n              // aria-hidden on the effective parent (or a transparent wrapper\n              // walked through on the way up) — upstream suppresses the rule.\n              return;\n            }\n            const parentRole = parentContext.role;\n            if (parentRole === undefined) {\n              // No non-transparent parent found (effectively root) — skip\n            } else if (!parentRole || !ROLES_REQUIRING_CONTEXT[role].includes(parentRole)) {\n              const roleAttr = node.attributes?.find((a) => a.name === 'role');\n              context.report({\n                node: roleAttr || node,\n                messageId: 'missingContext',\n                data: {\n                  role,\n                  requiredRoles: ROLES_REQUIRING_CONTEXT[role].join(', '),\n                },\n              });\n            }\n          }\n        }\n      },\n\n      'GlimmerElementNode:exit'() {\n        elementStack.pop();\n      },\n    };\n  },\n};\n\nfunction getRoleFromNode(node) {\n  const roleAttr = node.attributes?.find((a) => a.name === 'role');\n  if (roleAttr?.value?.type === 'GlimmerTextNode') {\n    return roleAttr.value.chars;\n  }\n  return null;\n}\n\nfunction hasAriaHiddenTrue(node) {\n  const attr = node.attributes?.find((a) => a.name === 'aria-hidden');\n  return attr?.value?.type === 'GlimmerTextNode' && attr.value.chars === 'true';\n}\n\n/**\n * Walk up the ancestor chain through transparent wrappers (named-block slots,\n * `<template>`, role=\"presentation\"/\"none\") checking `aria-hidden` at each\n * layer. Returns { ariaHidden, role } where:\n *   - `ariaHidden` is true if aria-hidden=\"true\" was seen on any traversed\n *     element (including transparent wrappers) up to and including the first\n *     non-transparent parent.\n *   - `role` is the role of the first non-transparent parent: a role string,\n *     null (element with no role), or undefined (no non-transparent parent).\n */\nfunction getParentContext(elementStack) {\n  for (let i = elementStack.length - 2; i >= 0; i--) {\n    const node = elementStack[i];\n    if (hasAriaHiddenTrue(node)) {\n      return { ariaHidden: true, role: undefined };\n    }\n    // Named blocks (`<:content>`) and the `<template>` wrapper are transparent\n    if (node.tag && (node.tag.startsWith(':') || node.tag === 'template')) {\n      continue;\n    }\n    const role = getRoleFromNode(node);\n    // presentation/none roles are transparent in the accessibility tree\n    if (role === 'presentation' || role === 'none') {\n      continue;\n    }\n    return { ariaHidden: false, role };\n  }\n  return { ariaHidden: false, role: undefined };\n}\n"
  },
  {
    "path": "lib/rules/template-require-each-key.js",
    "content": "const FIXED_KEY = 'key=\"@identity\"';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require key attribute in {{#each}} loops',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-each-key.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      requireEachKey:\n        '{{eachHelper}} helper requires a valid key value to avoid performance issues',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-each-key.js',\n      docs: 'docs/rule/require-each-key.md',\n      tests: 'test/unit/rules/require-each-key-test.js',\n    },\n  },\n\n  create(context) {\n    const VALID_AT_KEYS = new Set(['@index', '@identity']);\n\n    function isValidKey(pair) {\n      if (!pair.value || pair.value.type !== 'GlimmerStringLiteral') {\n        return true; // dynamic values are OK\n      }\n      const value = pair.value.value;\n      if (!value || value.trim() === '') {\n        return false; // empty key\n      }\n      if (value.startsWith('@') && !VALID_AT_KEYS.has(value)) {\n        return false; // invalid @ key\n      }\n      return true;\n    }\n\n    return {\n      GlimmerBlockStatement(node) {\n        if (node.path.type === 'GlimmerPathExpression' && node.path.original === 'each') {\n          const keyPair = node.hash && node.hash.pairs.find((pair) => pair.key === 'key');\n          if (!keyPair || !isValidKey(keyPair)) {\n            context.report({\n              node,\n              messageId: 'requireEachKey',\n              data: {\n                eachHelper: '{{#each}}',\n              },\n              fix(fixer) {\n                if (!keyPair) {\n                  const lastParam = node.params.at(-1) ?? node.path;\n\n                  return fixer.insertTextAfterRange(lastParam.range, ` ${FIXED_KEY}`);\n                }\n\n                return fixer.replaceTextRange(keyPair.range, FIXED_KEY);\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-form-method.js",
    "content": "// Form `method` attribute keywords:\n// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fs-method\nconst VALID_FORM_METHODS = ['POST', 'GET', 'DIALOG'];\n\nconst DEFAULT_CONFIG = {\n  allowedMethods: VALID_FORM_METHODS,\n};\n\nfunction parseConfig(config) {\n  if (config === false) {\n    return false;\n  }\n\n  if (config === true || config === undefined) {\n    return DEFAULT_CONFIG;\n  }\n\n  if (typeof config === 'object' && Array.isArray(config.allowedMethods)) {\n    const allowedMethods = config.allowedMethods.map((m) => String(m).toUpperCase());\n\n    // Check if all methods are valid\n    const hasAllValid = allowedMethods.every((m) => VALID_FORM_METHODS.includes(m));\n\n    if (hasAllValid) {\n      return { allowedMethods };\n    }\n  }\n\n  throw new Error(\n    'template-require-form-method: invalid configuration. Expected one of:\\n' +\n      '  * boolean - `true` to enable / `false` to disable\\n' +\n      '  * object -- An object with the following keys:\\n' +\n      `    * \\`allowedMethods\\` -- An array of allowed form \\`method\\` attribute values of \\`${VALID_FORM_METHODS}\\`\\n` +\n      `Received: ${JSON.stringify(config)}`\n  );\n}\n\nfunction makeErrorMessage(methods) {\n  return `All \\`<form>\\` elements should have \\`method\\` attribute with value of \\`${methods.join(',')}\\``;\n}\n\nfunction getFixedMethod(config) {\n  return config.allowedMethods[0];\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require form method attribute',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-form-method.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        oneOf: [\n          { type: 'boolean' },\n          {\n            type: 'object',\n            properties: {\n              allowedMethods: {\n                type: 'array',\n                items: {\n                  // Accept any string so case-insensitive values like \"get\"\n                  // still pass schema validation; parseConfig normalizes to\n                  // upper-case and throws on values not in VALID_FORM_METHODS.\n                  type: 'string',\n                },\n              },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      invalidMethod: '{{message}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-form-method.js',\n      docs: 'docs/rule/require-form-method.md',\n      tests: 'test/unit/rules/require-form-method-test.js',\n    },\n  },\n\n  create(context) {\n    // Default-enabled: parseConfig(undefined) returns DEFAULT_CONFIG.\n    // Pass `false` to explicitly disable the rule.\n    const rawOption = context.options[0];\n    const config = parseConfig(rawOption);\n\n    if (config === false) {\n      return {};\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'form') {\n          return;\n        }\n\n        const methodAttribute = node.attributes.find((attr) => attr.name === 'method');\n\n        if (!methodAttribute) {\n          context.report({\n            node,\n            messageId: 'invalidMethod',\n            data: {\n              message: makeErrorMessage(config.allowedMethods),\n            },\n            fix(fixer) {\n              return fixer.insertTextAfterRange(\n                [node.parts.at(-1).range[1], node.parts.at(-1).range[1]],\n                ` method=\"${getFixedMethod(config)}\"`\n              );\n            },\n          });\n          return;\n        }\n\n        // Check if it's a text value\n        if (methodAttribute.value && methodAttribute.value.type === 'GlimmerTextNode') {\n          const methodValue = methodAttribute.value.chars.toUpperCase();\n\n          if (!config.allowedMethods.includes(methodValue)) {\n            context.report({\n              node,\n              messageId: 'invalidMethod',\n              data: {\n                message: makeErrorMessage(config.allowedMethods),\n              },\n              fix(fixer) {\n                return fixer.replaceTextRange(\n                  methodAttribute.value.range,\n                  `\"${getFixedMethod(config)}\"`\n                );\n              },\n            });\n          }\n        }\n        // If it's a dynamic value (like {{foo}}), don't report\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-has-block-helper.js",
    "content": "const TRANSFORMATIONS = {\n  hasBlock: 'has-block',\n  hasBlockParams: 'has-block-params',\n};\n\nfunction getErrorMessage(name) {\n  return `\\`${name}\\` is deprecated. Use the \\`${TRANSFORMATIONS[name]}\\` helper instead.`;\n}\n\nfunction shouldWrapInSubExpression(node) {\n  const parent = node.parent;\n\n  if (!parent) {\n    return false;\n  }\n\n  if (parent.type === 'GlimmerBlockStatement') {\n    return true;\n  }\n\n  if (\n    (parent.type === 'GlimmerMustacheStatement' || parent.type === 'GlimmerSubExpression') &&\n    parent.path !== node\n  ) {\n    return true;\n  }\n\n  return false;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require (has-block) helper usage instead of hasBlock property',\n      category: 'Best Practices',\n      fixable: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-has-block-helper.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-has-block-helper.js',\n      docs: 'docs/rule/require-has-block-helper.md',\n      tests: 'test/unit/rules/require-has-block-helper-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    // Returns true if the identifier resolves to a JS binding. In GJS/GTS a\n    // user can legitimately `import { hasBlock } from 'somewhere'`; rewriting\n    // their reference to the `has-block` keyword would change semantics.\n    // Note: `node` here is the GlimmerPathExpression itself (this visitor is\n    // on PathExpression directly, not a wrapping MustacheStatement), so we\n    // read `node.original` rather than `node.path.original`.\n    function isJsScopeVariable(node) {\n      if (!sourceCode || !node.original) {\n        return false;\n      }\n      const name = node.original;\n      try {\n        let scope = sourceCode.getScope(node);\n        while (scope) {\n          if (scope.variables.some((v) => v.name === name)) {\n            return true;\n          }\n          scope = scope.upper;\n        }\n      } catch {\n        // sourceCode.getScope may not be available in .hbs-only mode; ignore.\n      }\n      return false;\n    }\n\n    return {\n      GlimmerPathExpression(node) {\n        const replacement = TRANSFORMATIONS[node.original];\n\n        if (replacement && !isJsScopeVariable(node)) {\n          context.report({\n            node,\n            message: getErrorMessage(node.original),\n            fix(fixer) {\n              return fixer.replaceTextRange(\n                node.range,\n                shouldWrapInSubExpression(node) ? `(${replacement})` : replacement\n              );\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-iframe-src-attribute.js",
    "content": "const ERROR_MESSAGE =\n  'Security Risk: `<iframe>` must include a static `src` attribute. Otherwise, CSP `frame-src` is bypassed and `about:blank` inherits parent origin, creating an elevated-privilege frame.';\nconst FIXED_SRC_ATTRIBUTE = 'src=\"about:blank\"';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require iframe elements to have src attribute',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-iframe-src-attribute.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      requireSrc: ERROR_MESSAGE,\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-iframe-src-attribute.js',\n      docs: 'docs/rule/require-iframe-src-attribute.md',\n      tests: 'test/unit/rules/require-iframe-src-attribute-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'iframe') {\n          return;\n        }\n\n        const hasSrcAttribute = node.attributes.find((attr) => attr.name === 'src');\n\n        if (!hasSrcAttribute) {\n          context.report({\n            node,\n            messageId: 'requireSrc',\n            fix(fixer) {\n              const firstModifier = node.modifiers[0];\n\n              if (firstModifier) {\n                return fixer.insertTextBeforeRange(\n                  [firstModifier.range[0], firstModifier.range[0]],\n                  `${FIXED_SRC_ATTRIBUTE} `\n                );\n              }\n\n              const lastAttribute = node.attributes.at(-1);\n\n              if (lastAttribute) {\n                return fixer.insertTextAfterRange(lastAttribute.range, ` ${FIXED_SRC_ATTRIBUTE}`);\n              }\n\n              const tagNameEnd = node.parts.at(-1)?.range[1] ?? node.range[0] + '<iframe'.length;\n\n              return fixer.insertTextAfterRange(\n                [tagNameEnd, tagNameEnd],\n                ` ${FIXED_SRC_ATTRIBUTE}`\n              );\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-iframe-title.js",
    "content": "'use strict';\n\n// Non-string literal AST nodes (boolean/null/undefined/number) don't represent\n// a meaningful author-provided title. Even though they would coerce to strings\n// at runtime (e.g. `true` → \"true\", `42` → \"42\"), those strings do not describe\n// the frame's content — the rule rejects the literal forms.\nconst INVALID_LITERAL_TYPES = new Set([\n  'GlimmerBooleanLiteral',\n  'GlimmerNullLiteral',\n  'GlimmerUndefinedLiteral',\n  'GlimmerNumberLiteral',\n]);\n\nfunction isInvalidTitleLiteralPath(path) {\n  return INVALID_LITERAL_TYPES.has(path?.type);\n}\n\nfunction getInvalidLiteralType(path) {\n  if (!path) {\n    return undefined;\n  }\n  switch (path.type) {\n    case 'GlimmerBooleanLiteral': {\n      return 'boolean';\n    }\n    case 'GlimmerNullLiteral': {\n      return 'null';\n    }\n    case 'GlimmerUndefinedLiteral': {\n      return 'undefined';\n    }\n    case 'GlimmerNumberLiteral': {\n      return 'number';\n    }\n    default: {\n      return undefined;\n    }\n  }\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require iframe elements to have a title attribute',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-iframe-title.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      // Five messageIds (missingTitle, emptyTitle, invalidTitleLiteral,\n      // duplicateTitleFirst, duplicateTitleOther) for richer diagnostic detail.\n      missingTitle: '<iframe> elements must have a unique title property.',\n      emptyTitle: '<iframe> elements must have a unique title property.',\n      invalidTitleLiteral:\n        '<iframe title> must be a non-empty string. Got {{literalType}} literal, which does not describe the frame contents.',\n      duplicateTitleFirst: 'This title is not unique. #{{index}}',\n      duplicateTitleOther:\n        '<iframe> elements must have a unique title property. Value title=\"{{title}}\" already used for different iframe. #{{index}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-iframe-title.js',\n      docs: 'docs/rule/require-iframe-title.md',\n      tests: 'test/unit/rules/require-iframe-title-test.js',\n    },\n  },\n  create(context) {\n    // Each entry: { value, node, index }\n    //  - value: trimmed title string\n    //  - node: original element node for the first occurrence\n    //  - index: duplicate-group index (1-based), assigned lazily on collision\n    const knownTitles = [];\n    let nextDuplicateIndex = 1;\n\n    // Process a statically-known title string (from a text node OR a\n    // mustache string literal OR a single-part concat). Handles the empty /\n    // whitespace / duplicate logic that's shared across those AST shapes.\n    function processStaticTitle(node, raw) {\n      const value = raw.trim();\n      if (value.length === 0) {\n        context.report({ node, messageId: 'emptyTitle' });\n        return;\n      }\n      // Duplicate check — reports BOTH the first and the current occurrence\n      // on every collision, sharing a `#N` index so users can correlate them.\n      // For three or more duplicates the first occurrence is therefore\n      // re-reported once per collision.\n      const existing = knownTitles.find((entry) => entry.value === value);\n      if (existing) {\n        if (existing.index === null) {\n          existing.index = nextDuplicateIndex++;\n        }\n        const index = existing.index;\n        context.report({\n          node: existing.node,\n          messageId: 'duplicateTitleFirst',\n          data: { index: String(index) },\n        });\n        context.report({\n          node,\n          messageId: 'duplicateTitleOther',\n          data: { title: raw, index: String(index) },\n        });\n      } else {\n        knownTitles.push({ value, node, index: null });\n      }\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'iframe') {\n          return;\n        }\n\n        // Skip if aria-hidden or hidden\n        const hasAriaHidden = node.attributes?.some((a) => a.name === 'aria-hidden');\n        const hasHidden = node.attributes?.some((a) => a.name === 'hidden');\n        if (hasAriaHidden || hasHidden) {\n          return;\n        }\n\n        // Check for title attribute\n        const titleAttr = node.attributes?.find((a) => a.name === 'title');\n        if (!titleAttr) {\n          context.report({ node, messageId: 'missingTitle' });\n          return;\n        }\n\n        if (titleAttr.value) {\n          switch (titleAttr.value.type) {\n            case 'GlimmerTextNode': {\n              processStaticTitle(node, titleAttr.value.chars);\n              break;\n            }\n            case 'GlimmerMustacheStatement': {\n              // Non-string literal mustaches — boolean / null / undefined /\n              // number — get a specific \"invalidTitleLiteral\" diagnostic\n              // because the literal coerces to a string at runtime that\n              // doesn't describe the frame contents.\n              if (isInvalidTitleLiteralPath(titleAttr.value.path)) {\n                context.report({\n                  node,\n                  messageId: 'invalidTitleLiteral',\n                  data: { literalType: getInvalidLiteralType(titleAttr.value.path) },\n                });\n                break;\n              }\n              // String-literal mustaches resolve to their static value — a\n              // non-empty literal supplies an accessible name the same as a\n              // text node. Empty / whitespace literals are flagged the same\n              // way as `title=\"\"` / `title=\"   \"`.\n              if (titleAttr.value.path?.type === 'GlimmerStringLiteral') {\n                processStaticTitle(node, titleAttr.value.path.value);\n              }\n              break;\n            }\n            case 'GlimmerConcatStatement': {\n              const parts = titleAttr.value.parts || [];\n              // Single-part concat wrapping a non-string literal — same\n              // diagnostic as the bare mustache form.\n              if (\n                parts.length === 1 &&\n                parts[0].type === 'GlimmerMustacheStatement' &&\n                isInvalidTitleLiteralPath(parts[0].path)\n              ) {\n                context.report({\n                  node,\n                  messageId: 'invalidTitleLiteral',\n                  data: { literalType: getInvalidLiteralType(parts[0].path) },\n                });\n                break;\n              }\n              // Single-part concat wrapping a string literal — resolve to\n              // the static value and apply the same checks as a text node.\n              if (\n                parts.length === 1 &&\n                parts[0].type === 'GlimmerMustacheStatement' &&\n                parts[0].path?.type === 'GlimmerStringLiteral'\n              ) {\n                processStaticTitle(node, parts[0].path.value);\n              }\n              break;\n            }\n            default: {\n              break;\n            }\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-input-label.js",
    "content": "function hasAttr(node, name) {\n  return node.attributes?.some((a) => a.name === name);\n}\n\nfunction isString(value) {\n  return typeof value === 'string';\n}\n\nfunction isRegExp(value) {\n  return value instanceof RegExp;\n}\n\nfunction allowedFormat(value) {\n  return isString(value) || isRegExp(value);\n}\n\nfunction parseConfig(config) {\n  if (config === false) {\n    return false;\n  }\n\n  if (config === true || config === undefined) {\n    return { labelTags: ['label'] };\n  }\n\n  if (config && typeof config === 'object' && Array.isArray(config.labelTags)) {\n    return {\n      labelTags: ['label', ...config.labelTags.filter(allowedFormat)],\n    };\n  }\n\n  return { labelTags: ['label'] };\n}\n\nfunction matchesLabelTag(tag, configuredTag) {\n  return isRegExp(configuredTag) ? configuredTag.test(tag) : configuredTag === tag;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require label for form input elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-input-label.md',\n      templateMode: 'both',\n    },\n    schema: [\n      {\n        anyOf: [\n          { type: 'boolean' },\n          {\n            type: 'object',\n            properties: {\n              labelTags: {\n                type: 'array',\n              },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      requireLabel: 'form elements require a valid associated label.',\n      multipleLabels: 'form elements should not have multiple labels.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-input-label.js',\n      docs: 'docs/rule/require-input-label.md',\n      tests: 'test/unit/rules/require-input-label-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseConfig(context.options[0]);\n    if (config === false) {\n      return {};\n    }\n\n    const filename = context.filename;\n    const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');\n    const elementStack = [];\n\n    // local name → original name ('Input' | 'Textarea')\n    // Only populated in GJS/GTS files via ImportDeclaration\n    const importedFormComponents = new Map();\n\n    function hasValidLabelParent() {\n      for (let i = elementStack.length - 1; i >= 0; i--) {\n        const entry = elementStack[i];\n        const hasMatchingLabelTag = config.labelTags.some((configuredTag) =>\n          matchesLabelTag(entry.tag, configuredTag)\n        );\n\n        if (hasMatchingLabelTag) {\n          if (entry.tag !== 'label') {\n            return true;\n          }\n\n          const children = entry.node.children || [];\n          return children.length > 1;\n        }\n      }\n      return false;\n    }\n\n    return {\n      ImportDeclaration(node) {\n        if (!isStrictMode) {\n          return;\n        }\n        if (node.source.value === '@ember/component') {\n          for (const specifier of node.specifiers) {\n            if (specifier.type === 'ImportSpecifier') {\n              const original = specifier.imported.name;\n              if (original === 'Input' || original === 'Textarea') {\n                importedFormComponents.set(specifier.local.name, original);\n              }\n            }\n          }\n        }\n      },\n\n      GlimmerElementNode(node) {\n        elementStack.push({ tag: node.tag, node });\n\n        const tag = node.tag;\n        // Is this tag one we should check?\n        // - Native <input>/<textarea>/<select> always.\n        // - <Input>/<Textarea> built-ins:\n        //   - In strict mode (.gjs/.gts): only if the tag resolves to a tracked\n        //     import from '@ember/component' (supports renames).\n        //   - In HBS: match by bare tag name.\n        const isNativeFormElement = tag === 'input' || tag === 'textarea' || tag === 'select';\n        const isBuiltinFormComponent = isStrictMode\n          ? importedFormComponents.has(tag)\n          : tag === 'Input' || tag === 'Textarea';\n\n        if (!isNativeFormElement && !isBuiltinFormComponent) {\n          return;\n        }\n\n        // Skip if input has type=\"hidden\"\n        const typeAttr = node.attributes?.find((a) => a.name === 'type');\n        if (typeAttr?.value?.type === 'GlimmerTextNode' && typeAttr.value.chars === 'hidden') {\n          return;\n        }\n\n        // Skip if has ...attributes (can't determine labelling)\n        if (hasAttr(node, '...attributes')) {\n          return;\n        }\n\n        let labelCount = 0;\n        const validLabel = hasValidLabelParent();\n        if (validLabel) {\n          labelCount++;\n        }\n\n        if (hasAttr(node, 'aria-label')) {\n          labelCount++;\n        }\n        if (hasAttr(node, 'aria-labelledby')) {\n          labelCount++;\n        }\n\n        if (labelCount === 1) {\n          return;\n        }\n\n        // An `id` may pair with a sibling `<label for>` we can't see in this\n        // template. Treat id-only as valid to avoid false positives, but don't\n        // count it toward labelCount — otherwise id + aria-label is wrongly\n        // flagged as multiple labels.\n        if (labelCount === 0 && hasAttr(node, 'id')) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId: labelCount === 0 ? 'requireLabel' : 'multipleLabels',\n        });\n      },\n      'GlimmerElementNode:exit'() {\n        elementStack.pop();\n      },\n\n      GlimmerMustacheStatement(node) {\n        // Classic {{input}}/{{textarea}} curly helpers only exist in HBS.\n        // In GJS/GTS, these identifiers are user-imported JS bindings with\n        // no relation to the classic helpers, so skip.\n        if (isStrictMode) {\n          return;\n        }\n\n        const name = node.path?.original;\n        if (name !== 'input' && name !== 'textarea') {\n          return;\n        }\n\n        const pairs = node.hash?.pairs || [];\n\n        function hasPair(key) {\n          return pairs.some((p) => p.key === key);\n        }\n\n        // Skip if type=\"hidden\" (literal string only)\n        const typePair = pairs.find((p) => p.key === 'type');\n        if (typePair?.value?.type === 'GlimmerStringLiteral' && typePair.value.value === 'hidden') {\n          return;\n        }\n\n        // If in a valid label, it's valid\n        if (hasValidLabelParent()) {\n          return;\n        }\n\n        // If has id, it's valid\n        if (hasPair('id')) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId: 'requireLabel',\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-input-type.js",
    "content": "'use strict';\n\n// See html-validate (https://html-validate.org) for the peer rule concept.\n\nconst { isNativeElement } = require('../utils/is-native-element');\n\nconst VALID_TYPES = new Set([\n  'button',\n  'checkbox',\n  'color',\n  'date',\n  'datetime-local',\n  'email',\n  'file',\n  'hidden',\n  'image',\n  'month',\n  'number',\n  'password',\n  'radio',\n  'range',\n  'reset',\n  'search',\n  'submit',\n  'tel',\n  'text',\n  'time',\n  'url',\n  'week',\n]);\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require input elements to have a valid type attribute',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-input-type.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          requireExplicit: {\n            type: 'boolean',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      missing: 'All `<input>` elements should have a `type` attribute',\n      invalid: '`<input type=\"{{value}}\">` is not a valid input type',\n    },\n  },\n\n  create(context) {\n    // Flagging a missing `type` is a style/consistency check, not a correctness\n    // one: `<input>` without `type` is spec-compliant (defaults to the Text\n    // state). Opt-in so teams that want parity with template-require-button-\n    // type can enable it without imposing it on others.\n    const requireExplicit = Boolean(context.options[0]?.requireExplicit);\n    const sourceCode = context.sourceCode || context.getSourceCode();\n\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'input') {\n          return;\n        }\n        // In strict GJS, a lowercase local binding can shadow the native\n        // `<input>` element. `isNativeElement` consults html/svg/mathml tag\n        // lists and checks bindings in the scope chain to filter out\n        // scope-shadowed cases.\n        if (!isNativeElement(node, sourceCode)) {\n          return;\n        }\n\n        const typeAttr = node.attributes?.find((attr) => attr.name === 'type');\n\n        if (!typeAttr) {\n          if (!requireExplicit) {\n            return;\n          }\n          context.report({\n            node,\n            messageId: 'missing',\n            fix(fixer) {\n              // Insert right after `<input` so the new attribute is the first\n              // one — avoids the fragile \"find end of open tag\" regex that can\n              // mis-place the attribute past the `/` in self-closing syntax.\n              const insertPos = node.range[0] + '<input'.length;\n              return fixer.insertTextBeforeRange([insertPos, insertPos], ' type=\"text\"');\n            },\n          });\n          return;\n        }\n\n        const value = typeAttr.value;\n\n        // Valueless attribute form (`<input type />`) — per HTML spec, a\n        // present-but-empty type attribute resolves to the missing-value\n        // default (\"Text state\"). That's the same runtime result as\n        // `type=\"\"`, which we already flag. Treat them consistently:\n        // flag as invalid('') and autofix to `type=\"text\"`.\n        if (!value) {\n          context.report({\n            node: typeAttr,\n            messageId: 'invalid',\n            data: { value: '' },\n            fix(fixer) {\n              return fixer.replaceText(typeAttr, 'type=\"text\"');\n            },\n          });\n          return;\n        }\n\n        if (value.type === 'GlimmerTextNode') {\n          const typeValue = value.chars.toLowerCase();\n          if (typeValue === '') {\n            context.report({\n              node: typeAttr,\n              messageId: 'invalid',\n              data: { value: '' },\n              fix(fixer) {\n                return fixer.replaceText(typeAttr, 'type=\"text\"');\n              },\n            });\n          } else if (!VALID_TYPES.has(typeValue)) {\n            context.report({\n              node: typeAttr,\n              messageId: 'invalid',\n              data: { value: value.chars },\n              fix(fixer) {\n                return fixer.replaceText(typeAttr, 'type=\"text\"');\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-lang-attribute.js",
    "content": "const tags = require('language-tags');\n\nconst DEFAULT_CONFIG = {\n  validateValues: true,\n};\n\nfunction isValidLangTag(value) {\n  if (!value || !value.trim()) {\n    return false;\n  }\n  return tags(value.trim()).valid();\n}\n\nfunction parseConfig(config) {\n  if (config === true || config === undefined) {\n    return DEFAULT_CONFIG;\n  }\n\n  if (config && typeof config === 'object') {\n    return {\n      validateValues:\n        'validateValues' in config ? config.validateValues : DEFAULT_CONFIG.validateValues,\n    };\n  }\n\n  return DEFAULT_CONFIG;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require lang attribute on html element',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-lang-attribute.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        anyOf: [\n          { type: 'boolean', enum: [true] },\n          {\n            type: 'object',\n            properties: {\n              validateValues: { type: 'boolean' },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      invalid: 'The `<html>` element must have the `lang` attribute with a valid value',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-lang-attribute.js',\n      docs: 'docs/rule/require-lang-attribute.md',\n      tests: 'test/unit/rules/require-lang-attribute-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseConfig(context.options[0]);\n\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'html') {\n          return;\n        }\n\n        const langAttr = node.attributes?.find((a) => a.name === 'lang');\n        if (!langAttr) {\n          context.report({ node, messageId: 'invalid' });\n          return;\n        }\n\n        if (!langAttr.value) {\n          context.report({ node, messageId: 'invalid' });\n          return;\n        }\n\n        if (langAttr.value.type === 'GlimmerTextNode') {\n          const value = langAttr.value.chars;\n\n          if (!value || !value.trim()) {\n            context.report({ node, messageId: 'invalid' });\n          } else if (config.validateValues && !isValidLangTag(value)) {\n            context.report({ node, messageId: 'invalid' });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-mandatory-role-attributes.js",
    "content": "const { roles } = require('aria-query');\nconst { AXObjectRoles, elementAXObjects } = require('axobject-query');\n\n// ARIA role values are whitespace-separated tokens compared ASCII-case-insensitively.\n// Returns the list of normalised tokens, or undefined when the attribute is\n// missing or dynamic.\nfunction getStaticRolesFromElement(node) {\n  const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n\n  if (roleAttr?.value?.type === 'GlimmerTextNode') {\n    return splitRoleTokens(roleAttr.value.chars);\n  }\n\n  return undefined;\n}\n\nfunction getStaticRolesFromMustache(node) {\n  const rolePair = node.hash?.pairs?.find((pair) => pair.key === 'role');\n\n  if (rolePair?.value?.type === 'GlimmerStringLiteral') {\n    return splitRoleTokens(rolePair.value.value);\n  }\n\n  return undefined;\n}\n\nfunction splitRoleTokens(value) {\n  if (!value) {\n    return undefined;\n  }\n  const tokens = value.trim().toLowerCase().split(/\\s+/u).filter(Boolean);\n  return tokens.length > 0 ? tokens : undefined;\n}\n\n// Reads the static lowercase value of `name` from either a GlimmerElementNode\n// (angle-bracket attributes) or a GlimmerMustacheStatement (hash pairs).\n// Returns undefined for dynamic values or missing attributes.\nfunction getStaticAttrValue(node, name) {\n  if (node?.type === 'GlimmerElementNode') {\n    const attr = node.attributes?.find((a) => a.name === name);\n    if (attr?.value?.type === 'GlimmerTextNode') {\n      return attr.value.chars?.toLowerCase();\n    }\n    return undefined;\n  }\n  if (node?.type === 'GlimmerMustacheStatement') {\n    const pair = node.hash?.pairs?.find((p) => p.key === name);\n    if (pair?.value?.type === 'GlimmerStringLiteral') {\n      return pair.value.value?.toLowerCase();\n    }\n    return undefined;\n  }\n  return undefined;\n}\n\n// In classic Handlebars (.hbs) `{{input}}` globally resolves to Ember's\n// built-in input helper, which renders a native <input>. In strict-mode\n// GJS/GTS there is no corresponding lowercase `input` export from\n// `@ember/component` (only the PascalCase `<Input>` component), so\n// `{{input}}` in strict mode is always a user-bound identifier and cannot\n// be assumed to render a native <input>. Treating it as native there\n// would silently skip required-ARIA checks on arbitrary components.\nfunction isClassicHbsFilename(context) {\n  const filename = context.filename || context.getFilename?.() || '';\n  return !filename.endsWith('.gjs') && !filename.endsWith('.gts');\n}\n\nfunction getTagName(node, context) {\n  if (node?.type === 'GlimmerElementNode') {\n    // HTML tag names are case-insensitive; normalize so <INPUT>/<Input> match\n    // the lowercase keys in AX_CONCEPTS_BY_TAG and the semantic-role maps.\n    return node.tag?.toLowerCase();\n  }\n  if (node?.type === 'GlimmerMustacheStatement' && node.path?.original === 'input') {\n    if (!context || isClassicHbsFilename(context)) {\n      return 'input';\n    }\n    // Strict-mode {{input}} — not the classic helper, can't claim native.\n    return null;\n  }\n  return null;\n}\n\n// Does this {element, role} pair match one of axobject-query's elementAXObjects\n// concepts? If so, the native element exposes the role's required ARIA state\n// automatically (e.g., <input type=checkbox> exposes aria-checked via the\n// `checked` attribute for both role=checkbox and role=switch).\n//\n// Mirrors jsx-a11y's `isSemanticRoleElement` util\n// (https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/isSemanticRoleElement.js).\n//\n// Pre-indexed at module load: elementAXObjects is static data, so we resolve\n// each concept's exposed-role set once (walking axObjectNames → AXObjectRoles\n// → role names) and bucket concepts by tag. That turns the per-call hot path\n// into O(concepts-for-this-tag × attrs-on-that-concept), which in practice\n// is a handful of entries. Benchmarked at ~12.5× faster than the naive full-\n// map walk on a realistic 200k-call workload.\nconst AX_CONCEPTS_BY_TAG = buildAxConceptsByTag();\n\nfunction buildAxConceptsByTag() {\n  const index = new Map();\n  for (const [concept, axObjectNames] of elementAXObjects) {\n    const conceptRoles = new Set();\n    for (const axName of axObjectNames) {\n      const axRoles = AXObjectRoles.get(axName);\n      if (!axRoles) {\n        continue;\n      }\n      for (const axRole of axRoles) {\n        conceptRoles.add(axRole.name);\n      }\n    }\n    const entry = { attributes: concept.attributes || [], roles: conceptRoles };\n    if (!index.has(concept.name)) {\n      index.set(concept.name, []);\n    }\n    index.get(concept.name).push(entry);\n  }\n  return index;\n}\n\nfunction isSemanticRoleElement(node, role, context) {\n  const tag = getTagName(node, context);\n  if (!tag || typeof role !== 'string') {\n    return false;\n  }\n  const entries = AX_CONCEPTS_BY_TAG.get(tag);\n  if (!entries) {\n    return false;\n  }\n  const targetRole = role.toLowerCase();\n  for (const { attributes, roles: conceptRoles } of entries) {\n    if (!conceptRoles.has(targetRole)) {\n      continue;\n    }\n    const allMatch = attributes.every((cAttr) => {\n      const nodeVal = getStaticAttrValue(node, cAttr.name);\n      if (nodeVal === undefined) {\n        return false;\n      }\n      if (cAttr.value === undefined) {\n        return true;\n      }\n      return nodeVal === String(cAttr.value).toLowerCase();\n    });\n    if (allMatch) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// For an ARIA role-fallback list like \"combobox listbox\", check required\n// attributes against the FIRST recognised role (the primary) per ARIA 1.2\n// role-fallback semantics — a user agent picks the first role it recognises.\n// Abstract roles (widget, input, command, section, … — ARIA §5.3) are\n// ontology categories, not valid authoring roles, so UAs skip them too.\n//\n// When the primary role is a semantic-role element (axobject-query says the\n// native element provides the required ARIA state natively — e.g. <input\n// type=checkbox role=switch>), the element is exempt: return { role, missing: null }.\n//\n// Diverges from jsx-a11y, which validates every recognised token.\nfunction getMissingRequiredAttributes(roleTokens, foundAriaAttributes, node, context) {\n  for (const role of roleTokens) {\n    const roleDefinition = roles.get(role);\n    if (!roleDefinition || roleDefinition.abstract) {\n      continue;\n    }\n    // Semantic-role elements expose required ARIA state natively — skip.\n    if (isSemanticRoleElement(node, role, context)) {\n      return { role, missing: null };\n    }\n    const requiredAttributes = Object.keys(roleDefinition.requiredProps);\n    const missingRequiredAttributes = requiredAttributes\n      .filter((requiredAttribute) => !foundAriaAttributes.includes(requiredAttribute))\n      // Sort for deterministic report order (aria-query's requiredProps\n      // iteration order is not guaranteed stable across versions).\n      .sort();\n    return {\n      role,\n      missing: missingRequiredAttributes.length > 0 ? missingRequiredAttributes : null,\n    };\n  }\n  return null;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require mandatory ARIA attributes for ARIA roles',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-mandatory-role-attributes.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      missingAttributes:\n        'The {{attributeWord}} {{attributes}} {{verb}} required by the role {{role}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-mandatory-role-attributes.js',\n      docs: 'docs/rule/require-mandatory-role-attributes.md',\n      tests: 'test/unit/rules/require-mandatory-role-attributes-test.js',\n    },\n  },\n\n  create(context) {\n    function reportMissingAttributes(node, role, missingRequiredAttributes) {\n      context.report({\n        node,\n        messageId: 'missingAttributes',\n        data: {\n          attributeWord: missingRequiredAttributes.length < 2 ? 'attribute' : 'attributes',\n          attributes: missingRequiredAttributes.join(', '),\n          verb: missingRequiredAttributes.length < 2 ? 'is' : 'are',\n          role,\n        },\n      });\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        const roleTokens = getStaticRolesFromElement(node);\n\n        if (!roleTokens) {\n          return;\n        }\n\n        const foundAriaAttributes = (node.attributes ?? [])\n          .filter((attribute) => attribute.name?.startsWith('aria-'))\n          .map((attribute) => attribute.name);\n\n        const result = getMissingRequiredAttributes(roleTokens, foundAriaAttributes, node, context);\n\n        if (result?.missing) {\n          reportMissingAttributes(node, result.role, result.missing);\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        const roleTokens = getStaticRolesFromMustache(node);\n\n        if (!roleTokens) {\n          return;\n        }\n\n        const foundAriaAttributes = (node.hash?.pairs ?? [])\n          .filter((pair) => pair.key.startsWith('aria-'))\n          .map((pair) => pair.key);\n\n        const result = getMissingRequiredAttributes(roleTokens, foundAriaAttributes, node, context);\n\n        if (result?.missing) {\n          reportMissingAttributes(node, result.role, result.missing);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-media-caption.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require captions for audio and video elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-media-caption.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      missingTrack: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-media-caption.js',\n      docs: 'docs/rule/require-media-caption.md',\n      tests: 'test/unit/rules/require-media-caption-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (node.tag !== 'audio' && node.tag !== 'video') {\n          return;\n        }\n\n        // Check if the element has a muted attribute that exempts it\n        const mutedAttr = node.attributes?.find((a) => a.name === 'muted');\n        if (mutedAttr) {\n          // muted with no value (boolean attribute like <video muted>) → valid\n          if (!mutedAttr.value) {\n            return;\n          }\n\n          const value = mutedAttr.value;\n\n          // muted=\"true\" or any string other than \"false\" → valid\n          if (value.type === 'GlimmerTextNode' && value.chars !== 'false') {\n            return;\n          }\n\n          // muted={{expr}} → valid (dynamic), unless it's a literal false (muted=false)\n          if (value.type === 'GlimmerMustacheStatement') {\n            const expr = value.path;\n            // muted=false → BooleanLiteral(false) → NOT muted, continue checking\n            if (expr?.type === 'GlimmerBooleanLiteral' && expr.value === false) {\n              // fall through to caption check\n            } else {\n              return;\n            }\n          }\n\n          // Any other dynamic value (e.g. muted=\"{{isMuted}}\" → ConcatStatement,\n          // or muted={{#if ...}}...{{/if}} → BlockStatement) → treat as exempt.\n          // These cannot be statically evaluated, so assume the element may be muted.\n          if (value.type !== 'GlimmerTextNode' && value.type !== 'GlimmerMustacheStatement') {\n            return;\n          }\n        }\n\n        // Check if there's a track element with kind=\"captions\" as a child\n        const hasCaption = node.children?.some((child) => {\n          if (child.type !== 'GlimmerElementNode' || child.tag !== 'track') {\n            return false;\n          }\n\n          const kindAttr = child.attributes?.find((a) => a.name === 'kind');\n          if (!kindAttr) {\n            return false;\n          }\n\n          if (kindAttr.value?.type === 'GlimmerTextNode') {\n            return kindAttr.value.chars.toLowerCase() === 'captions';\n          }\n\n          return false;\n        });\n\n        if (!hasCaption) {\n          context.report({\n            node,\n            messageId: 'missingTrack',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-presentational-children.js",
    "content": "const { roles } = require('aria-query');\n\n// Roles where descendants are presentational per the ARIA \"Children\n// Presentational\" rule. Derived from aria-query (role.childrenPresentational).\n// https://www.w3.org/TR/wai-aria-1.2/#childrenArePresentational\nconst ROLES_REQUIRING_PRESENTATIONAL_CHILDREN = new Set(\n  [...roles.keys()].filter((role) => roles.get(role).childrenPresentational)\n);\n\n// Tags that do not have semantic meaning\nconst NON_SEMANTIC_TAGS = new Set([\n  'span',\n  'div',\n  'basefont',\n  'big',\n  'blink',\n  'center',\n  'font',\n  'marquee',\n  's',\n  'spacer',\n  'strike',\n  'tt',\n  'u',\n]);\n\nconst SKIPPED_TAGS = new Set([\n  // SVG tags can contain a lot of special child tags\n  // Instead of marking all possible SVG child tags as NON_SEMANTIC_TAG,\n  // we skip checking this rule for presentational SVGs\n  'svg',\n]);\n\nfunction getRoleValue(node) {\n  const roleAttr = node.attributes?.find((a) => a.name === 'role');\n  if (!roleAttr || roleAttr.value?.type !== 'GlimmerTextNode') {\n    return null;\n  }\n  return roleAttr.value.chars;\n}\n\nfunction hasPresentationalRole(node) {\n  const role = getRoleValue(node);\n  return role === 'presentation';\n}\n\nfunction findAllSemanticDescendants(children, nonSemanticTags, results) {\n  for (const child of children || []) {\n    if (child.type === 'GlimmerElementNode') {\n      // If child tag starts with ':', it's a named block — skip it but recurse into its children\n      if (child.tag.startsWith(':')) {\n        findAllSemanticDescendants(child.children, nonSemanticTags, results);\n        continue;\n      }\n\n      const isPresentational = hasPresentationalRole(child);\n\n      // Include this node in results if it's not non-semantic and not presentational\n      if (!nonSemanticTags.has(child.tag) && !isPresentational) {\n        results.push(child);\n      }\n\n      // Always recurse into children — even if the current node is presentational,\n      // its descendants may still be semantic and need to be reported\n      findAllSemanticDescendants(child.children, nonSemanticTags, results);\n    }\n  }\n  return results;\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require presentational elements to only contain presentational children',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-presentational-children.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          additionalNonSemanticTags: {\n            type: 'array',\n            items: { type: 'string' },\n            uniqueItems: true,\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      invalid:\n        '<{{parent}}> has a role of {{role}}, it cannot have semantic descendants like <{{child}}>',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-presentational-children.js',\n      docs: 'docs/rule/require-presentational-children.md',\n      tests: 'test/unit/rules/require-presentational-children-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const nonSemanticTags = new Set([\n      ...NON_SEMANTIC_TAGS,\n      ...(options.additionalNonSemanticTags || []),\n    ]);\n\n    return {\n      GlimmerElementNode(node) {\n        const roleAttr = node.attributes?.find((a) => a.name === 'role');\n        if (!roleAttr || roleAttr.value?.type !== 'GlimmerTextNode') {\n          return;\n        }\n\n        const role = roleAttr.value.chars;\n\n        if (ROLES_REQUIRING_PRESENTATIONAL_CHILDREN.has(role)) {\n          if (SKIPPED_TAGS.has(node.tag)) {\n            return;\n          }\n\n          const semanticDescendants = findAllSemanticDescendants(\n            node.children,\n            nonSemanticTags,\n            []\n          );\n          for (const semanticChild of semanticDescendants) {\n            context.report({\n              node: semanticChild,\n              messageId: 'invalid',\n              data: {\n                parent: node.tag,\n                role,\n                child: semanticChild.tag,\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-splattributes.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require splattributes usage in component templates',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-splattributes.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      rootElement: 'The root element in this template should use `...attributes`',\n      atLeastOne: 'At least one element in this template should use `...attributes`',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-splattributes.js',\n      docs: 'docs/rule/require-splattributes.md',\n      tests: 'test/unit/rules/require-splattributes-test.js',\n    },\n  },\n\n  create(context) {\n    let foundSplattributes = false;\n\n    return {\n      GlimmerAttrNode(node) {\n        if (node.name === '...attributes') {\n          foundSplattributes = true;\n        }\n      },\n\n      'GlimmerTemplate:exit'(node) {\n        if (foundSplattributes) {\n          return;\n        }\n\n        const body = node.body ?? [];\n        const effectiveBody =\n          body.length === 1 &&\n          body[0].type === 'GlimmerElementNode' &&\n          body[0].tag === 'template' &&\n          Array.isArray(body[0].children)\n            ? body[0].children\n            : body;\n        const elementNodes = effectiveBody.filter((child) => child.type === 'GlimmerElementNode');\n        const significantTextNodes = effectiveBody.filter(\n          (child) => child.type === 'GlimmerTextNode' && child.chars.trim() !== ''\n        );\n        const hasOnlyOneElement = elementNodes.length === 1 && significantTextNodes.length === 0;\n\n        if (hasOnlyOneElement) {\n          context.report({\n            node: elementNodes[0],\n            messageId: 'rootElement',\n          });\n        } else {\n          context.report({\n            node,\n            messageId: 'atLeastOne',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-strict-mode.js",
    "content": "const ERROR_MESSAGE =\n  'Templates are required to be in strict mode. Consider refactoring to template tag format.';\n\nfunction isStrictModeFile(filePath) {\n  return filePath?.endsWith('.gjs') || filePath?.endsWith('.gts');\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require templates to be in strict mode',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-strict-mode.md',\n      templateMode: 'loose',\n    },\n    fixable: null,\n    schema: [],\n    messages: {},\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-strict-mode.js',\n      docs: 'docs/rule/require-strict-mode.md',\n      tests: 'test/unit/rules/require-strict-mode-test.js',\n    },\n  },\n\n  create(context) {\n    const filePath = context.filename;\n\n    return {\n      'GlimmerTemplate:exit'(node) {\n        if (!isStrictModeFile(filePath)) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-valid-alt-text.js",
    "content": "const { getStaticAttrValue } = require('../utils/static-attr-value');\n\nconst REDUNDANT_WORDS = ['image', 'photo', 'picture', 'logo', 'spacer'];\n\nfunction findAttr(node, name) {\n  return node.attributes?.find((a) => a.name === name);\n}\n\nfunction hasAttr(node, name) {\n  return node.attributes?.some((a) => a.name === name);\n}\n\n/**\n * Returns true if the named attribute is present with a non-empty, non-whitespace\n * static value, OR present with a dynamic (mustache/concat) value. Dynamic values\n * are assumed to resolve to a meaningful name at runtime (we can't verify at lint\n * time). Static empty-string / whitespace-only values return false — applied to\n * alt / aria-label / aria-labelledby / title. The empty-name treatment aligns with\n * ACCNAME 1.2 §4.3.2's aria-label step (2D), which normalizes empty/whitespace\n * to \"no name\"; we apply the same normalization to the other fallbacks.\n * (Why a name is needed: WCAG SC 4.1.2 — \"all user interface components have a\n * name and role that can be programmatically determined.\")\n *\n * NOTE: This does not validate that aria-labelledby IDREFs reference existing IDs.\n * Rule consumers should layer that check separately if needed.\n */\nfunction hasNonEmptyTextAttr(node, name) {\n  const attr = findAttr(node, name);\n  if (!attr?.value) {\n    return false;\n  }\n  // Resolve mustache-literal / single-part concat forms to their static\n  // string via the shared helper. `aria-label={{\"\"}}` / `aria-label=\"{{\"\"}}\"`\n  // now normalise to the empty string and are treated the same as the\n  // text-node empty value.\n  const resolved = getStaticAttrValue(attr.value);\n  if (resolved === undefined) {\n    // Genuinely dynamic — assume truthy (can't verify at lint time).\n    return true;\n  }\n  return resolved.trim() !== '';\n}\n\nfunction hasAnyNonEmptyTextAttr(node, names) {\n  return names.some((name) => hasNonEmptyTextAttr(node, name));\n}\n\nfunction getTextValue(attr) {\n  if (!attr?.value) {\n    return undefined;\n  }\n  if (attr.value.type === 'GlimmerTextNode') {\n    return attr.value.chars;\n  }\n  return undefined;\n}\n\nfunction getNormalizedAltText(altAttr) {\n  if (!altAttr?.value) {\n    return null;\n  }\n  if (altAttr.value.type === 'GlimmerTextNode') {\n    return altAttr.value.chars.trim().toLowerCase();\n  }\n  if (altAttr.value.type === 'GlimmerConcatStatement') {\n    const parts = (altAttr.value.parts || [])\n      .filter((p) => p.type === 'GlimmerTextNode')\n      .map((p) => p.chars)\n      .join(' ')\n      .trim()\n      .toLowerCase();\n    return parts === '' ? null : parts;\n  }\n  return null;\n}\n\nfunction hasChildren(node) {\n  return (\n    node.children &&\n    node.children.some((child) => {\n      if (child.type === 'GlimmerTextNode') {\n        return child.chars.trim().length > 0;\n      }\n      return true;\n    })\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require valid alt text for images and other elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-valid-alt-text.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      imgMissing: 'All `<img>` tags must have an alt attribute',\n      imgRedundant:\n        'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.',\n      imgAltEqualsSrc: 'The alt text must not be the same as the image source',\n      imgNumericAlt: 'A number is not valid alt text',\n      imgRolePresentation:\n        'The `alt` attribute should be empty if `<img>` has `role` of `none` or `presentation`',\n      inputImage:\n        'All <input> elements with type=\"image\" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',\n      objectMissing:\n        'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby attributes.',\n      areaMissing:\n        'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-valid-alt-text.js',\n      docs: 'docs/rule/require-valid-alt-text.md',\n      tests: 'test/unit/rules/require-valid-alt-text-test.js',\n    },\n  },\n  create(context) {\n    return {\n      // eslint-disable-next-line complexity\n      GlimmerElementNode(node) {\n        // Skip hidden elements\n        if (hasAttr(node, 'hidden')) {\n          return;\n        }\n\n        const ariaHidden = findAttr(node, 'aria-hidden');\n        if (ariaHidden) {\n          const val = getTextValue(ariaHidden);\n          if (val === 'true') {\n            return;\n          }\n        }\n\n        // Skip elements with ...attributes (splattributes)\n        if (hasAttr(node, '...attributes')) {\n          return;\n        }\n\n        const tag = node.tag;\n\n        switch (tag) {\n          case 'img': {\n            const altAttr = findAttr(node, 'alt');\n            const roleAttr = findAttr(node, 'role');\n            const srcAttr = findAttr(node, 'src');\n\n            // Check role=none/presentation with non-empty alt\n            if (altAttr && roleAttr) {\n              const roleValue = getTextValue(roleAttr);\n              const altValue = getTextValue(altAttr);\n              if (\n                roleValue &&\n                ['none', 'presentation'].includes(roleValue.trim().toLowerCase()) &&\n                altValue !== ''\n              ) {\n                context.report({ node, messageId: 'imgRolePresentation' });\n              }\n            }\n\n            if (!altAttr) {\n              context.report({ node, messageId: 'imgMissing' });\n              return;\n            }\n\n            // Check alt === src\n            const altValue = getTextValue(altAttr);\n            const srcValue = getTextValue(srcAttr);\n            if (altValue !== undefined && srcValue !== undefined && altValue === srcValue) {\n              context.report({ node, messageId: 'imgAltEqualsSrc' });\n              return;\n            }\n\n            // Check numeric-only alt and redundant words\n            const normalizedAlt = getNormalizedAltText(altAttr);\n            if (normalizedAlt !== null) {\n              if (/^\\d+$/.test(normalizedAlt)) {\n                context.report({ node, messageId: 'imgNumericAlt' });\n              } else {\n                const words = normalizedAlt.split(' ');\n                const hasRedundant = REDUNDANT_WORDS.some((w) => words.includes(w));\n                if (hasRedundant) {\n                  context.report({ node, messageId: 'imgRedundant' });\n                }\n              }\n            }\n\n            break;\n          }\n          case 'input': {\n            // Only check input type=\"image\"\n            const typeAttr = findAttr(node, 'type');\n            const typeVal = getTextValue(typeAttr);\n            if (typeVal !== 'image') {\n              return;\n            }\n\n            // Empty-string aria-label/aria-labelledby/alt provides no accessible\n            // name — require a non-empty fallback value.\n            if (!hasAnyNonEmptyTextAttr(node, ['aria-label', 'aria-labelledby', 'alt'])) {\n              context.report({ node, messageId: 'inputImage' });\n            }\n\n            break;\n          }\n          case 'object': {\n            const roleAttr = findAttr(node, 'role');\n            const roleValue = getTextValue(roleAttr);\n\n            if (\n              hasAnyNonEmptyTextAttr(node, ['aria-label', 'aria-labelledby', 'title']) ||\n              hasChildren(node) ||\n              (roleValue && ['presentation', 'none'].includes(roleValue))\n            ) {\n              return;\n            }\n\n            context.report({ node, messageId: 'objectMissing' });\n\n            break;\n          }\n          case 'area': {\n            if (!hasAnyNonEmptyTextAttr(node, ['aria-label', 'aria-labelledby', 'alt'])) {\n              context.report({ node, messageId: 'areaMissing' });\n            }\n\n            break;\n          }\n          // No default\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-valid-form-groups.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nconst FORM_ELEMENTS = new Set(['input']);\n\nfunction hasRoleGroup(node) {\n  const roleAttr = node.attributes?.find((attr) => attr.name === 'role');\n  return roleAttr && roleAttr.value?.type === 'GlimmerTextNode' && roleAttr.value.chars === 'group';\n}\n\nfunction hasAriaLabel(node) {\n  return node.attributes?.some((attr) => attr.name === 'aria-labelledby');\n}\n\nfunction isValidFormGroup(node) {\n  if (node.tag === 'fieldset' || node.tag === 'legend') {\n    return true;\n  }\n\n  return hasRoleGroup(node) && hasAriaLabel(node);\n}\n\nfunction hasMultipleFormElementsInParentScope(node) {\n  const parent = node.parent;\n\n  if (!parent || parent.type !== 'GlimmerElementNode') {\n    return false;\n  }\n\n  const elementChildren =\n    parent.children?.filter((child) => child.type === 'GlimmerElementNode') || [];\n  const formElements = elementChildren.filter((child) => FORM_ELEMENTS.has(child.tag));\n\n  return formElements.length > 1;\n}\n\nfunction hasValidGroupingAncestor(node) {\n  let parent = node.parent;\n\n  while (parent) {\n    if (parent.type === 'GlimmerElementNode' && isValidFormGroup(parent)) {\n      return true;\n    }\n\n    parent = parent.parent;\n  }\n\n  return false;\n}\n\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description:\n        'require grouped form controls to have fieldset/legend or WAI-ARIA group labeling',\n      category: 'Accessibility',\n      recommendedGjs: false,\n      recommendedGts: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-valid-form-groups.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      requireValidFormGroups:\n        'Grouped form controls should have appropriate semantics such as fieldset and legend or WAI-ARIA labels',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-valid-form-groups.js',\n      docs: 'docs/rule/require-valid-form-groups.md',\n      tests: 'test/unit/rules/require-valid-form-groups-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        if (!FORM_ELEMENTS.has(node.tag)) {\n          return;\n        }\n\n        if (!hasMultipleFormElementsInParentScope(node)) {\n          return;\n        }\n\n        if (hasValidGroupingAncestor(node)) {\n          return;\n        }\n\n        context.report({\n          node,\n          messageId: 'requireValidFormGroups',\n        });\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-require-valid-named-block-naming-format.js",
    "content": "const FORMAT = {\n  CAMEL_CASE: 'camelCase',\n  KEBAB_CASE: 'kebab-case',\n};\n\nfunction camelize(str) {\n  return str\n    .split('-')\n    .map((word, index) => {\n      if (index === 0) {\n        return word;\n      }\n      return word.charAt(0).toUpperCase() + word.slice(1);\n    })\n    .join('');\n}\n\nfunction dasherize(str) {\n  return str.replaceAll(/([A-Z])/g, '-$1').toLowerCase();\n}\n\nconst FORMAT_METHOD = {\n  [FORMAT.CAMEL_CASE]: camelize,\n  [FORMAT.KEBAB_CASE]: dasherize,\n};\n\nconst DEFAULT_FORMAT = FORMAT.CAMEL_CASE;\n\nfunction parseConfig(config) {\n  if (config === false) {\n    return false;\n  }\n\n  if (config === undefined || config === true) {\n    return DEFAULT_FORMAT;\n  }\n\n  const formats = Object.values(FORMAT);\n  if (typeof config === 'string' && formats.includes(config)) {\n    return config;\n  }\n\n  return DEFAULT_FORMAT;\n}\n\nfunction getStringQuote(text) {\n  return text.startsWith(\"'\") ? \"'\" : '\"';\n}\n\nfunction isHasBlockNode(node) {\n  return node.path?.original === 'has-block' || node.path?.original === 'has-block-params';\n}\n\nfunction isYieldNode(node) {\n  return node.path?.original === 'yield';\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require valid named block naming format',\n      category: 'Best Practices',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-valid-named-block-naming-format.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        oneOf: [{ type: 'boolean' }, { type: 'string', enum: ['camelCase', 'kebab-case'] }],\n      },\n    ],\n    messages: {\n      invalidFormat:\n        'Named blocks are required to use the \"{{format}}\" naming format. Please change \"{{actual}}\" to \"{{expected}}\".',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/require-valid-named-block-naming-format.js',\n      docs: 'docs/rule/require-valid-named-block-naming-format.md',\n      tests: 'test/unit/rules/require-valid-named-block-naming-format-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n    const config = parseConfig(context.options[0]);\n\n    if (config === false) {\n      return {};\n    }\n\n    const formatMethod = FORMAT_METHOD[config];\n\n    function checkNamedBlockName(name, node) {\n      const expectedName = formatMethod(name);\n\n      if (name !== expectedName) {\n        const quote = getStringQuote(sourceCode.getText(node));\n\n        context.report({\n          node,\n          messageId: 'invalidFormat',\n          data: {\n            format: config,\n            actual: name,\n            expected: expectedName,\n          },\n          fix(fixer) {\n            return fixer.replaceText(node, `${quote}${expectedName}${quote}`);\n          },\n        });\n      }\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        if (isYieldNode(node) && node.hash?.pairs) {\n          const toPair = node.hash.pairs.find((pair) => pair.key === 'to');\n          if (toPair?.value?.type === 'GlimmerStringLiteral') {\n            checkNamedBlockName(toPair.value.value, toPair.value);\n          }\n        }\n\n        if (isHasBlockNode(node) && node.params?.[0]) {\n          if (node.params[0].type === 'GlimmerStringLiteral') {\n            checkNamedBlockName(node.params[0].value, node.params[0]);\n          }\n        }\n      },\n\n      GlimmerSubExpression(node) {\n        if (isHasBlockNode(node) && node.params?.[0]) {\n          if (node.params[0].type === 'GlimmerStringLiteral') {\n            checkNamedBlockName(node.params[0].value, node.params[0]);\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-self-closing-void-elements.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require self-closing on void elements',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-self-closing-void-elements.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        oneOf: [{ type: 'boolean' }, { type: 'string', enum: ['require'] }],\n      },\n    ],\n    messages: {\n      redundantSelfClosing: 'Self-closing a void element is redundant',\n      requireSelfClosing: 'Self-closing a void element is required',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/self-closing-void-elements.js',\n      docs: 'docs/rule/self-closing-void-elements.md',\n      tests: 'test/unit/rules/self-closing-void-elements-test.js',\n    },\n  },\n\n  create(context) {\n    const VOID_ELEMENTS = new Set([\n      'area',\n      'base',\n      'br',\n      'col',\n      'command',\n      'embed',\n      'hr',\n      'img',\n      'input',\n      'keygen',\n      'link',\n      'meta',\n      'param',\n      'source',\n      'track',\n      'wbr',\n    ]);\n\n    const sourceCode = context.sourceCode;\n    const config = context.options[0] ?? true;\n\n    if (config === false) {\n      return {};\n    }\n\n    const requireSelfClosing = config === 'require';\n\n    return {\n      GlimmerElementNode(node) {\n        if (!VOID_ELEMENTS.has(node.tag)) {\n          return;\n        }\n\n        if (requireSelfClosing) {\n          if (!node.selfClosing) {\n            const source = sourceCode.getText(node).trim();\n\n            context.report({\n              node,\n              messageId: 'requireSelfClosing',\n              fix(fixer) {\n                return fixer.replaceText(node, source.replace(/>$/, '/>'));\n              },\n            });\n          }\n        } else {\n          if (node.selfClosing) {\n            const source = sourceCode.getText(node).trim();\n\n            context.report({\n              node,\n              messageId: 'redundantSelfClosing',\n              fix(fixer) {\n                const replacement = node.blockParams?.length\n                  ? source.replace(/\\/>$/, '>')\n                  : source.replace(/\\s*\\/>$/, '>');\n\n                return fixer.replaceText(node, replacement);\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-simple-modifiers.js",
    "content": "function isModifierHelper(node) {\n  return node.path?.original === 'modifier';\n}\n\nfunction isValidFirstParam(node) {\n  return (\n    node.type === 'GlimmerStringLiteral' || (node.type === 'GlimmerPathExpression' && node.original)\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require simple modifier syntax',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-simple-modifiers.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      invalidFirstArgument:\n        'The modifier helper should have a string or a variable name containing the modifier name as a first argument.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/simple-modifiers.js',\n      docs: 'docs/rule/simple-modifiers.md',\n      tests: 'test/unit/rules/simple-modifiers-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      // This catches {{(modifier ...)}} expressions\n      GlimmerSubExpression(node) {\n        if (!isModifierHelper(node)) {\n          return;\n        }\n\n        const firstParam = node.params?.[0];\n\n        if (!firstParam) {\n          context.report({\n            node,\n            messageId: 'invalidFirstArgument',\n          });\n          return;\n        }\n\n        if (!isValidFirstParam(firstParam)) {\n          context.report({\n            node: firstParam,\n            messageId: 'invalidFirstArgument',\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-simple-unless.js",
    "content": "function isUnless(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'unless';\n}\n\nfunction isIf(node) {\n  return node.path?.type === 'GlimmerPathExpression' && node.path.original === 'if';\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require simple conditions in unless blocks',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-simple-unless.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          allowlist: { type: 'array', items: { type: 'string' } },\n          denylist: { type: 'array', items: { type: 'string' } },\n          maxHelpers: { type: 'integer' },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      followingElseBlock: 'Using an `else` block with `unless` should be avoided.',\n      asElseUnlessBlock: 'Using an `else unless` block should be avoided.',\n      withHelper: '{{message}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/simple-unless.js',\n      docs: 'docs/rule/simple-unless.md',\n      tests: 'test/unit/rules/simple-unless-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const allowlist = options.allowlist || [];\n    const denylist = options.denylist || [];\n    const maxHelpers = options.maxHelpers === undefined ? 1 : options.maxHelpers;\n    const sourceCode = context.sourceCode;\n\n    function isElseUnlessBlock(node) {\n      if (!node) {\n        return false;\n      }\n      if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'unless') {\n        const text = sourceCode.getText(node);\n        return text.startsWith('{{else ');\n      }\n      return false;\n    }\n\n    /**\n     * Build a fixer for the _withHelper case.\n     * Converts `unless` to `if` and wraps the first param with `(not ...)`.\n     * Special-cases when the first param is already `(not ...)`:\n     *   - single arg: `unless (not x)` → `if x`\n     *   - multiple args: `unless (not x y)` → `if (or x y)`\n     */\n    function buildWithHelperFix(node) {\n      return function fix(fixer) {\n        const nodeText = sourceCode.getText(node);\n        const firstParam = node.params[0];\n        const firstParamText = sourceCode.getText(firstParam);\n\n        // Replace 'unless' with 'if' in the keyword\n        let newText = nodeText.replace(/^({{#?)unless/, '$1if');\n\n        if (firstParam.type === 'GlimmerSubExpression' && firstParam.path?.original === 'not') {\n          // Special case: first param is (not ...)\n          if (firstParam.params.length > 1) {\n            // (not x y) → (or x y)\n            const innerParamsText = firstParam.params.map((p) => sourceCode.getText(p)).join(' ');\n            newText = newText.replace(firstParamText, `(or ${innerParamsText})`);\n          } else {\n            // (not x) → x — unwrap the not\n            const innerText = sourceCode.getText(firstParam.params[0]);\n            newText = newText.replace(firstParamText, innerText);\n          }\n        } else {\n          // Wrap with (not ...)\n          newText = newText.replace(firstParamText, `(not ${firstParamText})`);\n        }\n\n        // Also fix the closing tag for block statements\n        if (node.type === 'GlimmerBlockStatement') {\n          newText = newText.replace(/{{\\/unless}}$/, '{{/if}}');\n        }\n\n        return fixer.replaceText(node, newText);\n      };\n    }\n\n    /**\n     * Build a fixer for the _followingElseBlock case (simple else, no else-if).\n     * Swaps body/inverse and changes unless→if.\n     */\n    function buildFollowingElseBlockFix(node) {\n      return function fix(fixer) {\n        const programStart = node.program.range[0];\n        const bodyText = sourceCode.text.slice(programStart, node.program.range[1]);\n        const inverseText = sourceCode.text.slice(node.inverse.range[0], node.inverse.range[1]);\n        const openingTag = sourceCode.text\n          .slice(node.range[0], programStart)\n          .replace(/^({{#)unless/, '$1if');\n        // Result shape: {{#if cond}}inverse{{else}}body{{/if}}\n        return fixer.replaceText(node, `${openingTag}${inverseText}{{else}}${bodyText}{{/if}}`);\n      };\n    }\n\n    function checkWithHelper(node) {\n      let helperCount = 0;\n      let nextParams = node.params || [];\n\n      do {\n        const currentParams = nextParams;\n        nextParams = [];\n\n        for (const param of currentParams) {\n          if (param.type === 'GlimmerSubExpression') {\n            helperCount++;\n            const helperName = param.path?.original || '';\n\n            if (maxHelpers > -1 && helperCount > maxHelpers) {\n              context.report({\n                node: param,\n                messageId: 'withHelper',\n                data: {\n                  message: `Using {{unless}} in combination with other helpers should be avoided. MaxHelpers: ${maxHelpers}`,\n                },\n                fix: buildWithHelperFix(node),\n              });\n              return;\n            }\n\n            if (allowlist.length > 0 && !allowlist.includes(helperName)) {\n              context.report({\n                node: param,\n                messageId: 'withHelper',\n                data: {\n                  message: `Using {{unless}} in combination with other helpers should be avoided. Allowed helper${allowlist.length > 1 ? 's' : ''}: ${allowlist}`,\n                },\n                fix: buildWithHelperFix(node),\n              });\n              return;\n            }\n\n            if (denylist.length > 0 && denylist.includes(helperName)) {\n              context.report({\n                node: param,\n                messageId: 'withHelper',\n                data: {\n                  message: `Using {{unless}} in combination with other helpers should be avoided. Restricted helper${denylist.length > 1 ? 's' : ''}: ${denylist}`,\n                },\n                fix: buildWithHelperFix(node),\n              });\n              return;\n            }\n\n            if (param.params) {\n              nextParams.push(...param.params);\n            }\n          }\n        }\n      } while (nextParams.some((p) => p.type === 'GlimmerSubExpression'));\n    }\n\n    return {\n      GlimmerMustacheStatement(node) {\n        if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'unless') {\n          if (node.params?.[0]?.path) {\n            checkWithHelper(node);\n          }\n        }\n      },\n\n      GlimmerBlockStatement(node) {\n        const nodeInverse = node.inverse;\n\n        if (nodeInverse && nodeInverse.body?.length > 0) {\n          if (isUnless(node)) {\n            // Check for {{#unless}}...{{else if}}\n            if (nodeInverse.body[0] && isIf(nodeInverse.body[0])) {\n              // Not fixable (ETL: _followingElseIfBlock, isFixable=false)\n              context.report({\n                node: node.program || node,\n                messageId: 'followingElseBlock',\n              });\n            } else {\n              // {{#unless}}...{{else}} — fixable\n              context.report({\n                node: node.program || node,\n                messageId: 'followingElseBlock',\n                fix: buildFollowingElseBlockFix(node),\n              });\n            }\n          } else if (isElseUnlessBlock(nodeInverse.body[0])) {\n            // {{#if}}...{{else unless}} — not fixable (ETL: isFixable=false)\n            context.report({\n              node: nodeInverse.body[0],\n              messageId: 'asElseUnlessBlock',\n            });\n          }\n        } else if (isUnless(node) && node.params?.[0]?.path) {\n          checkWithHelper(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-sort-invocations.js",
    "content": "/* eslint-disable unicorn/consistent-function-scoping, unicorn/prefer-at */\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'require sorted attributes and modifiers',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-sort-invocations.md',\n      templateMode: 'both',\n    },\n    fixable: 'code',\n    schema: [],\n    messages: {\n      attributeOrder: '`{{attributeName}}` must appear after `{{expectedAfter}}`',\n      modifierOrder: '`{{{{modifierName}}}}` must appear after `{{{{expectedAfter}}}}`',\n      hashPairOrder: '`{{hashPairName}}` must appear after `{{expectedAfter}}`',\n      splattributesOrder: '`...attributes` must appear after modifiers',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/sort-invocations.js',\n      docs: 'docs/rule/sort-invocations.md',\n      tests: 'test/unit/rules/sort-invocations-test.js',\n    },\n  },\n\n  create(context) {\n    const sourceCode = context.sourceCode;\n\n    // Glimmer attribute and modifier nodes can have trailing whitespace\n    // absorbed into their range, so we strip the trailing whitespace from\n    // each side and re-append it at the end of the swapped output.\n    function createSwapFix(fixer, nodeA, nodeB) {\n      const rawA = sourceCode.getText(nodeA);\n      const rawB = sourceCode.getText(nodeB);\n      const contentA = rawA.trimEnd();\n      const contentB = rawB.trimEnd();\n      const separator = sourceCode.text.slice(nodeA.range[0] + contentA.length, nodeB.range[0]);\n      const trailing = rawB.slice(contentB.length);\n      return fixer.replaceTextRange(\n        [nodeA.range[0], nodeB.range[1]],\n        contentB + separator + contentA + trailing\n      );\n    }\n\n    function getAttributeName(node) {\n      return node.name;\n    }\n\n    function getAttributePosition(node) {\n      const name = getAttributeName(node);\n\n      if (name.startsWith('@')) {\n        return 1; // Arguments first\n      }\n\n      if (name === '...attributes') {\n        return 3; // Splattributes last\n      }\n\n      return 2; // Regular attributes in the middle\n    }\n\n    function getHashPairName(node) {\n      return node.key;\n    }\n\n    function getModifierName(node) {\n      if (node.path.type !== 'GlimmerPathExpression') {\n        return '';\n      }\n\n      return node.path.original;\n    }\n\n    function compareAttributes(a, b) {\n      const positionA = getAttributePosition(a);\n      const positionB = getAttributePosition(b);\n\n      if (positionA !== positionB) {\n        return positionA - positionB;\n      }\n\n      const nameA = getAttributeName(a);\n      const nameB = getAttributeName(b);\n\n      return nameA.localeCompare(nameB);\n    }\n\n    function compareHashPairs(a, b) {\n      const nameA = getHashPairName(a);\n      const nameB = getHashPairName(b);\n\n      return nameA.localeCompare(nameB);\n    }\n\n    function compareModifiers(a, b) {\n      const nameA = getModifierName(a);\n      const nameB = getModifierName(b);\n\n      if (nameA !== nameB) {\n        return nameA.localeCompare(nameB);\n      }\n\n      // For 'on' modifiers, sort by event name\n      if (nameA === 'on' && a.params && b.params && a.params.length > 0 && b.params.length > 0) {\n        const eventA = a.params[0];\n        const eventB = b.params[0];\n\n        if (eventA.type === 'GlimmerStringLiteral' && eventB.type === 'GlimmerStringLiteral') {\n          return eventA.value.localeCompare(eventB.value);\n        }\n      }\n\n      return 0;\n    }\n\n    function getUnsortedAttributeIndex(attributes) {\n      return attributes.findIndex((attribute, index) => {\n        if (index === attributes.length - 1) {\n          return false;\n        }\n\n        return compareAttributes(attribute, attributes[index + 1]) > 0;\n      });\n    }\n\n    function getUnsortedHashPairIndex(pairs) {\n      return pairs.findIndex((hashPair, index) => {\n        if (index === pairs.length - 1) {\n          return false;\n        }\n\n        return compareHashPairs(hashPair, pairs[index + 1]) > 0;\n      });\n    }\n\n    function getUnsortedModifierIndex(modifiers) {\n      return modifiers.findIndex((modifier, index) => {\n        if (index === modifiers.length - 1) {\n          return false;\n        }\n\n        return compareModifiers(modifier, modifiers[index + 1]) > 0;\n      });\n    }\n\n    function canSkipSplattributesLast(node) {\n      const { attributes, modifiers } = node;\n\n      if (!attributes || attributes.length === 0 || !modifiers || modifiers.length === 0) {\n        return true;\n      }\n\n      const splattributes = attributes.at(-1);\n      const lastModifier = modifiers.at(-1);\n\n      if (!splattributes || splattributes.name !== '...attributes' || !lastModifier) {\n        return true;\n      }\n\n      // Check that ...attributes appears after the last modifier\n      const splattributesPosition = splattributes.loc.start;\n      const lastModifierPosition = lastModifier.loc.start;\n\n      if (splattributesPosition.line > lastModifierPosition.line) {\n        return true;\n      }\n\n      return (\n        splattributesPosition.line === lastModifierPosition.line &&\n        splattributesPosition.column > lastModifierPosition.column\n      );\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        const { attributes, modifiers } = node;\n\n        if (attributes && attributes.length > 1) {\n          const index = getUnsortedAttributeIndex(attributes);\n\n          if (index !== -1) {\n            context.report({\n              node: attributes[index],\n              messageId: 'attributeOrder',\n              data: {\n                attributeName: getAttributeName(attributes[index]),\n                expectedAfter: getAttributeName(attributes[index + 1]),\n              },\n              fix(fixer) {\n                return createSwapFix(fixer, attributes[index], attributes[index + 1]);\n              },\n            });\n          }\n        }\n\n        if (modifiers && modifiers.length > 1) {\n          const index = getUnsortedModifierIndex(modifiers);\n\n          if (index !== -1) {\n            context.report({\n              node: modifiers[index],\n              messageId: 'modifierOrder',\n              data: {\n                modifierName: getModifierName(modifiers[index]),\n                expectedAfter: getModifierName(modifiers[index + 1]),\n              },\n              fix(fixer) {\n                return createSwapFix(fixer, modifiers[index], modifiers[index + 1]);\n              },\n            });\n          }\n        }\n\n        if (!canSkipSplattributesLast(node)) {\n          const splattributes = attributes.at(-1);\n\n          // Swap ...attributes past the first modifier that appears after it;\n          // ESLint's fix loop continues until splattributes is fully sorted.\n          // canSkipSplattributesLast guarantees at least one such modifier exists.\n          const firstModifierAfter = modifiers.find(\n            (mod) =>\n              mod.loc.start.line > splattributes.loc.start.line ||\n              (mod.loc.start.line === splattributes.loc.start.line &&\n                mod.loc.start.column > splattributes.loc.start.column)\n          );\n\n          const splatFixFn = (fixer) => createSwapFix(fixer, splattributes, firstModifierAfter);\n\n          // When ...attributes is the only attribute, report as attributeOrder\n          // (the ordering issue is that ...attributes should appear after modifiers)\n          if (attributes.length === 1) {\n            context.report({\n              node: splattributes,\n              messageId: 'attributeOrder',\n              data: {\n                attributeName: '...attributes',\n                expectedAfter: 'modifiers',\n              },\n              fix: splatFixFn,\n            });\n          } else {\n            context.report({\n              node: splattributes,\n              messageId: 'splattributesOrder',\n              fix: splatFixFn,\n            });\n          }\n        }\n      },\n\n      GlimmerBlockStatement(node) {\n        if (node.hash && node.hash.pairs && node.hash.pairs.length > 1) {\n          const index = getUnsortedHashPairIndex(node.hash.pairs);\n\n          if (index !== -1) {\n            context.report({\n              node: node.hash.pairs[index],\n              messageId: 'hashPairOrder',\n              data: {\n                hashPairName: getHashPairName(node.hash.pairs[index]),\n                expectedAfter: getHashPairName(node.hash.pairs[index + 1]),\n              },\n              fix(fixer) {\n                return createSwapFix(fixer, node.hash.pairs[index], node.hash.pairs[index + 1]);\n              },\n            });\n          }\n        }\n      },\n\n      GlimmerMustacheStatement(node) {\n        if (node.hash && node.hash.pairs && node.hash.pairs.length > 1) {\n          const index = getUnsortedHashPairIndex(node.hash.pairs);\n\n          if (index !== -1) {\n            // Component invocations with a string positional param (e.g. {{component \"ui/button\" ...}})\n            // treat hash pairs as component attributes\n            const isComponentInvocation =\n              node.path &&\n              node.path.original === 'component' &&\n              node.params &&\n              node.params.length > 0 &&\n              node.params[0].type === 'GlimmerStringLiteral';\n\n            const fixFn = function (fixer) {\n              return createSwapFix(fixer, node.hash.pairs[index], node.hash.pairs[index + 1]);\n            };\n\n            if (isComponentInvocation) {\n              context.report({\n                node: node.hash.pairs[index],\n                messageId: 'attributeOrder',\n                data: {\n                  attributeName: getHashPairName(node.hash.pairs[index]),\n                  expectedAfter: getHashPairName(node.hash.pairs[index + 1]),\n                },\n                fix: fixFn,\n              });\n            } else {\n              context.report({\n                node: node.hash.pairs[index],\n                messageId: 'hashPairOrder',\n                data: {\n                  hashPairName: getHashPairName(node.hash.pairs[index]),\n                  expectedAfter: getHashPairName(node.hash.pairs[index + 1]),\n                },\n                fix: fixFn,\n              });\n            }\n          }\n        }\n      },\n\n      GlimmerSubExpression(node) {\n        if (node.hash && node.hash.pairs && node.hash.pairs.length > 1) {\n          const index = getUnsortedHashPairIndex(node.hash.pairs);\n\n          if (index !== -1) {\n            context.report({\n              node: node.hash.pairs[index],\n              messageId: 'hashPairOrder',\n              data: {\n                hashPairName: getHashPairName(node.hash.pairs[index]),\n                expectedAfter: getHashPairName(node.hash.pairs[index + 1]),\n              },\n              fix(fixer) {\n                return createSwapFix(fixer, node.hash.pairs[index], node.hash.pairs[index + 1]);\n              },\n            });\n          }\n        }\n      },\n    };\n  },\n};\n/* eslint-enable unicorn/consistent-function-scoping, unicorn/prefer-at */\n"
  },
  {
    "path": "lib/rules/template-splat-attributes-only.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'disallow ...spread other than ...attributes',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-splat-attributes-only.md',\n      templateMode: 'both',\n    },\n    schema: [],\n    messages: {\n      onlyAttributes: 'Only `...attributes` can be applied to elements',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/splat-attributes-only.js',\n      docs: 'docs/rule/splat-attributes-only.md',\n      tests: 'test/unit/rules/splat-attributes-only-test.js',\n    },\n  },\n  create(context) {\n    return {\n      GlimmerAttrNode(node) {\n        if (node.name?.startsWith('...') && node.name !== '...attributes') {\n          context.report({ node, messageId: 'onlyAttributes' });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-style-concatenation.js",
    "content": "/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'disallow string concatenation in inline styles',\n      category: 'Best Practices',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-style-concatenation.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [],\n    messages: {\n      unexpected:\n        'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/style-concatenation.js',\n      docs: 'docs/rule/style-concatenation.md',\n      tests: 'test/unit/rules/style-concatenation-test.js',\n    },\n  },\n\n  create(context) {\n    return {\n      GlimmerElementNode(node) {\n        const styleAttr = node.attributes?.find((a) => a.name === 'style');\n\n        if (!styleAttr || !styleAttr.value) {\n          return;\n        }\n\n        // Check if style attribute uses concatenation\n        if (styleAttr.value.type === 'GlimmerConcatStatement') {\n          context.report({\n            node: styleAttr,\n            messageId: 'unexpected',\n          });\n        }\n\n        // Check for mustache containing concat helper\n        if (styleAttr.value.type === 'GlimmerMustacheStatement') {\n          const path = styleAttr.value.path;\n          if (path && path.type === 'GlimmerPathExpression' && path.original === 'concat') {\n            context.report({\n              node: styleAttr,\n              messageId: 'unexpected',\n            });\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-table-groups.js",
    "content": "const ALLOWED_TABLE_CHILDREN = ['caption', 'colgroup', 'thead', 'tbody', 'tfoot'];\nconst CONTROL_FLOW_START_MARK = 0;\nconst CONTROL_FLOW_END_MARK = 1;\n\nfunction dasherize(str) {\n  return str\n    .replaceAll('::', '/')\n    .replaceAll(/([\\da-z])([A-Z])/g, '$1-$2')\n    .toLowerCase();\n}\n\nfunction isControlFlowHelper(node) {\n  if (node.type !== 'GlimmerBlockStatement' && node.type !== 'GlimmerMustacheStatement') {\n    return false;\n  }\n  const name = node.path?.original;\n  return ['if', 'unless', 'each', 'each-in', 'let', 'with'].includes(name);\n}\n\nfunction isIfOrUnless(node) {\n  const name = node.path?.original;\n  return name === 'if' || name === 'unless';\n}\n\nfunction getEffectiveChildren(children) {\n  return (children || []).flatMap((child) => {\n    if (isControlFlowHelper(child)) {\n      if (isIfOrUnless(child) && child.program && child.inverse) {\n        return [\n          CONTROL_FLOW_START_MARK,\n          ...getEffectiveChildren(child.program?.body || child.children || []),\n          CONTROL_FLOW_END_MARK,\n          CONTROL_FLOW_START_MARK,\n          ...getEffectiveChildren(child.inverse?.body || []),\n          CONTROL_FLOW_END_MARK,\n        ];\n      }\n      const body = child.program?.body || child.children || child.body?.body || [];\n      return getEffectiveChildren(body);\n    }\n    return [child];\n  });\n}\n\nfunction isAllowedTableChild(child, internalTags) {\n  switch (child.type) {\n    case 'GlimmerElementNode': {\n      const idx = ALLOWED_TABLE_CHILDREN.indexOf(child.tag);\n      if (idx > -1) {\n        return { allowed: true, indices: [idx] };\n      }\n      // Check @tagName attribute\n      const tagNameAttr = child.attributes?.find((a) => a.name === '@tagName');\n      if (tagNameAttr) {\n        const val = tagNameAttr.value?.type === 'GlimmerTextNode' ? tagNameAttr.value.chars : null;\n        const tIdx = ALLOWED_TABLE_CHILDREN.indexOf(val);\n        return { allowed: tIdx > -1, indices: tIdx > -1 ? [tIdx] : [] };\n      }\n      // Check custom component mapping\n      const dasherized = dasherize(child.tag);\n      const possibleIndices = internalTags.get(dasherized) || [];\n      if (possibleIndices.length > 0) {\n        return { allowed: true, indices: possibleIndices };\n      }\n      return { allowed: false };\n    }\n    case 'GlimmerBlockStatement':\n    case 'GlimmerMustacheStatement': {\n      // Check tagName hash pair\n      const tagNamePair = child.hash?.pairs?.find((p) => p.key === 'tagName');\n      if (tagNamePair) {\n        const val = tagNamePair.value?.value || tagNamePair.value?.chars;\n        const idx = ALLOWED_TABLE_CHILDREN.indexOf(val);\n        return { allowed: idx > -1, indices: idx > -1 ? [idx] : [] };\n      }\n      if (child.path?.original === 'yield') {\n        return { allowed: true, indices: [] };\n      }\n      const possibleIndices = internalTags.get(child.path?.original) || [];\n      if (possibleIndices.length > 0) {\n        return { allowed: true, indices: possibleIndices };\n      }\n      return { allowed: false };\n    }\n    case 'GlimmerCommentStatement':\n    case 'GlimmerMustacheCommentStatement': {\n      return { allowed: true, indices: [] };\n    }\n    case 'GlimmerTextNode': {\n      return { allowed: !/\\S/.test(child.chars || ''), indices: [] };\n    }\n    default: {\n      return { allowed: false };\n    }\n  }\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'problem',\n    docs: {\n      description: 'require table elements to use table grouping elements',\n      category: 'Accessibility',\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-table-groups.md',\n      templateMode: 'both',\n    },\n    fixable: null,\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          'allowed-table-components': { type: 'array', items: { type: 'string' } },\n          'allowed-caption-components': { type: 'array', items: { type: 'string' } },\n          'allowed-colgroup-components': { type: 'array', items: { type: 'string' } },\n          'allowed-thead-components': { type: 'array', items: { type: 'string' } },\n          'allowed-tbody-components': { type: 'array', items: { type: 'string' } },\n          'allowed-tfoot-components': { type: 'array', items: { type: 'string' } },\n        },\n        additionalProperties: false,\n      },\n    ],\n    messages: {\n      missing: 'Tables must have a table group (thead, tbody or tfoot).',\n      ordering:\n        'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/table-groups.js',\n      docs: 'docs/rule/table-groups.md',\n      tests: 'test/unit/rules/table-groups-test.js',\n    },\n  },\n\n  create(context) {\n    const options = context.options[0] || {};\n    const outerTags = new Set(options['allowed-table-components'] || []);\n    const internalTags = new Map();\n\n    const componentKeys = [\n      'allowed-caption-components',\n      'allowed-colgroup-components',\n      'allowed-thead-components',\n      'allowed-tbody-components',\n      'allowed-tfoot-components',\n    ];\n\n    for (const [index, key] of componentKeys.entries()) {\n      if (options[key]) {\n        for (const comp of options[key]) {\n          if (!internalTags.has(comp)) {\n            internalTags.set(comp, []);\n          }\n          internalTags.get(comp).push(index);\n        }\n      }\n    }\n\n    function isTableElement(node) {\n      if (node.tag === 'table') {\n        return true;\n      }\n      if (outerTags.has(dasherize(node.tag))) {\n        return true;\n      }\n      const tagNameAttr = node.attributes?.find((a) => a.name === '@tagName');\n      if (tagNameAttr) {\n        const val = tagNameAttr.value?.type === 'GlimmerTextNode' ? tagNameAttr.value.chars : null;\n        return val === 'table';\n      }\n      return false;\n    }\n\n    return {\n      GlimmerElementNode(node) {\n        if (!isTableElement(node)) {\n          return;\n        }\n\n        const children = getEffectiveChildren(node.children);\n\n        let currentAllowedMinimumIndices = new Set([0]);\n        const scopedIndices = [];\n\n        for (const child of children) {\n          if (child === CONTROL_FLOW_START_MARK) {\n            scopedIndices.push(currentAllowedMinimumIndices);\n            currentAllowedMinimumIndices = new Set(\n              scopedIndices.reduce((acc, indices) => [...acc, ...indices])\n            );\n            continue;\n          }\n          if (child === CONTROL_FLOW_END_MARK) {\n            currentAllowedMinimumIndices = scopedIndices.pop();\n            continue;\n          }\n\n          const { allowed, indices } = isAllowedTableChild(child, internalTags);\n          if (!allowed) {\n            context.report({ node, messageId: 'missing' });\n            return;\n          }\n\n          if (indices.length > 0) {\n            const newAllowedMinimumIndices = new Set(\n              [...currentAllowedMinimumIndices].flatMap((currentIndex) =>\n                indices.filter((newIndex) => newIndex >= currentIndex)\n              )\n            );\n            if (newAllowedMinimumIndices.size === 0) {\n              context.report({ node, messageId: 'ordering' });\n              return;\n            }\n            currentAllowedMinimumIndices = newAllowedMinimumIndices;\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/template-template-length.js",
    "content": "const DEFAULT_MAX_LENGTH = 200;\nconst DEFAULT_MIN_LENGTH = 5;\n\nconst DEFAULT_CONFIG = {\n  max: DEFAULT_MAX_LENGTH,\n  min: DEFAULT_MIN_LENGTH,\n};\n\nfunction isValidConfigObjectFormat(config) {\n  for (const key of Object.keys(config)) {\n    const value = config[key];\n\n    if (key !== 'min' && key !== 'max') {\n      return false;\n    }\n\n    if (!Number.isInteger(value)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nfunction parseConfig(config) {\n  const configType = typeof config;\n\n  switch (configType) {\n    case 'boolean': {\n      return config ? DEFAULT_CONFIG : {};\n    }\n    case 'object': {\n      if (config === null) {\n        break;\n      }\n\n      if (isValidConfigObjectFormat(config)) {\n        return config;\n      }\n      break;\n    }\n    case 'undefined': {\n      return {};\n    }\n    // No default\n  }\n\n  throw new Error(\n    'The ember/template-template-length rule accepts: boolean, or object with integer \"min\" and/or \"max\" keys.'\n  );\n}\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce template size constraints',\n      category: 'Stylistic Issues',\n      recommendedGjs: false,\n      recommendedGts: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-template-length.md',\n      templateMode: 'both',\n    },\n    schema: [\n      {\n        oneOf: [\n          { type: 'boolean' },\n          {\n            type: 'object',\n            properties: {\n              min: { type: 'integer' },\n              max: { type: 'integer' },\n            },\n            additionalProperties: false,\n          },\n        ],\n      },\n    ],\n    messages: {\n      tooLong: 'Template length of {{length}} exceeds {{max}}',\n      tooShort: 'Template length of {{length}} is smaller than {{min}}',\n    },\n    originallyFrom: {\n      name: 'ember-template-lint',\n      rule: 'lib/rules/template-length.js',\n      docs: 'docs/rule/template-length.md',\n      tests: 'test/unit/rules/template-length-test.js',\n    },\n  },\n\n  create(context) {\n    const config = parseConfig(context.options[0]);\n\n    if (!config.min && !config.max) {\n      return {};\n    }\n\n    return {\n      'GlimmerTemplate:exit'(node) {\n        const length = node.loc.end.line - node.loc.start.line + 1;\n\n        if (config.max && length > config.max) {\n          context.report({\n            node,\n            messageId: 'tooLong',\n            data: { length, max: config.max },\n          });\n        } else if (config.min && length < config.min) {\n          context.report({\n            node,\n            messageId: 'tooShort',\n            data: { length, min: config.min },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/use-brace-expansion.js",
    "content": "'use strict';\n\nconst types = require('../utils/types');\nconst ember = require('../utils/ember');\nconst { getImportIdentifier } = require('../utils/import');\n\n//------------------------------------------------------------------------------\n// General rule - Use brace expansion\n//------------------------------------------------------------------------------\n\nconst ERROR_MESSAGE = 'Use brace expansion';\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'layout',\n    docs: {\n      description: 'enforce usage of brace expansion in computed property dependent keys',\n      category: 'Computed Properties',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/use-brace-expansion.md',\n    },\n    fixable: null,\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    let importedEmberName;\n    let importedComputedName;\n\n    return {\n      ImportDeclaration(node) {\n        if (node.source.value === 'ember') {\n          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');\n        }\n        if (node.source.value === '@ember/object') {\n          importedComputedName =\n            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');\n        }\n      },\n\n      CallExpression(node) {\n        if (!ember.isComputedProp(node, importedEmberName, importedComputedName)) {\n          return;\n        }\n\n        const matchesBraces = (x) => Boolean(/[{}]/g.test(x)); // eslint-disable-line unicorn/consistent-function-scoping\n        const hasBraces = (arr) => arr.some(matchesBraces);\n        const beforeBraces = (arr) => arr.slice(0, arr.indexOf(arr.find(matchesBraces)));\n        // eslint-disable-next-line unicorn/consistent-function-scoping\n        const arrayDeepEqual = (a, b) =>\n          a.length === b.length && a.reduce((acc, e, i) => acc && e === b[i], true);\n\n        const problem = node.arguments\n          .filter(\n            (arg) =>\n              types.isLiteral(arg) && typeof arg.value === 'string' && arg.value.includes('.')\n          )\n          .map((e) => e.value.split('.'))\n          .find(\n            (prop, i, props) =>\n              props.filter((e) => {\n                const propHasBraces = hasBraces(prop);\n                const eHasBraces = hasBraces(e);\n\n                if (propHasBraces && eHasBraces) {\n                  return arrayDeepEqual(beforeBraces(e), beforeBraces(prop));\n                } else if (!propHasBraces && !eHasBraces) {\n                  return prop[0] === e[0];\n                }\n\n                const withBraces = propHasBraces ? prop : e;\n                const withoutBraces = propHasBraces ? e : prop;\n                const shareable = beforeBraces(withBraces);\n                return arrayDeepEqual(shareable, withoutBraces.slice(0, shareable.length));\n              }).length > 1\n          );\n\n        if (problem) {\n          context.report({\n            node,\n            message: ERROR_MESSAGE,\n            loc: {\n              // Only report the string arguments (dependent keys) of the computed property (not the entire function body).\n              start: node.arguments[0].loc.start,\n              end: node.arguments.at(-2).loc.end,\n            },\n          });\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/use-ember-data-rfc-395-imports.js",
    "content": "'use strict';\n\nconst MAPPINGS = require('@ember-data/rfc395-data');\nconst { buildFix, buildMessage, getFullNames, isDestructuring } = require('../utils/new-module');\nconst { getSourceModuleNameForIdentifier } = require('../utils/import');\n\n/**\n * This function returns an object like this:\n * {\n *   'ember-data/model': {\n *     default: ['@ember-data/model'],\n *   },\n * }\n */\nfunction oldDataImportsReducer(acc, mapping) {\n  const obj = Object.create(null);\n  obj[mapping.export] = [mapping.replacement.module, mapping.replacement.export];\n  acc[mapping.module] = obj; // eslint-disable-line no-param-reassign\n\n  return acc;\n}\n\nconst OLD_DATA_IMPORTS = MAPPINGS.reduce(oldDataImportsReducer, Object.create(null));\n\n/**\n * This function returns an object like this:\n * {\n *   \"DS.Model\": {\n *     global: \"DS.Model\",\n *     export: \"default\",\n *     localName: \"Model\",\n *     module: \"@ember-data/model\" <- replacement\n *   }\n * }\n */\nfunction globalsReducer(acc, mapping) {\n  const { global, replacement, localName } = mapping;\n  if (global in acc) {\n    return acc;\n  }\n\n  const obj = {\n    global,\n    export: replacement.export,\n    localName,\n    module: replacement.module,\n  };\n\n  acc[global] = obj; // eslint-disable-line no-param-reassign\n\n  return acc;\n}\n\nconst GLOBALS = MAPPINGS.reduce(globalsReducer, Object.create(null));\n\nconst ERROR_MESSAGE =\n  'Imports from @ember-data packages should be preferred over imports from ember-data';\n\n//------------------------------------------------------------------------------\n// Rule Definition - Use \"Ember Data Packages\" from Ember RFC #395\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of `@ember-data/` package imports instead `ember-data`',\n      category: 'Ember Data',\n      recommended: true,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/use-ember-data-rfc-395-imports.md',\n    },\n    fixable: 'code',\n    schema: [],\n  },\n\n  ERROR_MESSAGE,\n\n  create(context) {\n    //----------------------------------------------------------------------\n    // Variables\n    //----------------------------------------------------------------------\n\n    const message = ERROR_MESSAGE;\n\n    //----------------------------------------------------------------------\n    // Public\n    //----------------------------------------------------------------------\n\n    return {\n      /**\n       * turn `import Model from \"ember-data/model\"`\n       * into `import Model from \"@ember-data/model\"`\n       * or\n       * `import attr from \"ember-data/attr\"`\n       * into `import { attr } from \"@ember-data/attr\"`\n       */\n      ImportDeclaration(node) {\n        // 'ember-data/store' is a valid import again: https://deprecations.emberjs.com/id/ember-data-deprecate-legacy-imports/\n        if (node.source.value === 'ember-data/store') {\n          return;\n        }\n\n        if (node.source.value in OLD_DATA_IMPORTS) {\n          const fix = buildFix(node, OLD_DATA_IMPORTS);\n          context.report({ node, message, fix });\n          return;\n        }\n\n        if (\n          node.source.value === 'ember-data' ||\n          (node.source.value.startsWith('ember-data/') &&\n            !node.source.value.startsWith('ember-data/types/registries/'))\n        ) {\n          context.report({ node, message });\n        }\n      },\n\n      /**\n       * warn against `const { Model } = DS`\n       * and against `const { Model } = ED` (only) if ED is imported from ember-data\n       */\n      VariableDeclarator(node) {\n        // Filter out non-ember-data variable declarations\n        if (\n          !isDestructuring(node) ||\n          getSourceModuleNameForIdentifier(context, node.init) !== 'ember-data'\n        ) {\n          return;\n        }\n\n        const properties = node.id.properties;\n        // Iterate through the destructured properties and report them\n        for (const item of properties) {\n          const key = item.key.name;\n          const match = GLOBALS[`DS.${key}`];\n          if (match) {\n            const message = buildMessage({\n              node: item,\n              customKey: key === item.value.name ? null : item.value.name,\n              key,\n              match,\n              type: item.type,\n            });\n            context.report({ node: item, message });\n          }\n        }\n      },\n\n      /**\n       * warn against DS usage in `name: DS.attr('string')`\n       * taken from\n       * https://github.com/ember-cli/eslint-plugin-ember/blob/v6.7.1/lib/rules/new-module-imports.js#L63\n       */\n      'MemberExpression > Identifier[name=DS]'(node) {\n        // filter out \"foo.DS\"\n        if (node.parent.object !== node) {\n          return;\n        }\n\n        // build an array of full expression names\n        // e.g. [DS.Model, DS.JSONSerializer]\n\n        let fullName = 'DS';\n        const fullNames = getFullNames(fullName, node);\n\n        // find a matching expression starting at the end\n        for (const element of fullNames) {\n          fullName = element;\n\n          const key = fullName.replace(/^DS\\./, '');\n          const match = GLOBALS[fullName];\n\n          // if a given global path does not exist in `mappings.json` there is no\n          // JS module import for it, so do not report the error\n          if (match) {\n            const message = buildMessage({ node, fullName, key, match });\n            context.report({ node, message });\n            break;\n          }\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/rules/use-ember-get-and-set.js",
    "content": "'use strict';\n\nconst ember = require('../utils/ember');\nconst utils = require('../utils/utils');\nconst types = require('../utils/types');\n\nconst collectObjectPatternBindings = utils.collectObjectPatternBindings;\nconst isIdentifier = types.isIdentifier;\nconst isThisExpression = types.isThisExpression;\n\n//------------------------------------------------------------------------------\n// General - use get and set\n//------------------------------------------------------------------------------\n\n/** @type {import('eslint').Rule.RuleModule} */\nmodule.exports = {\n  meta: {\n    type: 'suggestion',\n    docs: {\n      description: 'enforce usage of `Ember.get` and `Ember.set`',\n      category: 'Ember Object',\n      recommended: false,\n      url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/use-ember-get-and-set.md',\n    },\n    fixable: 'code',\n    schema: [\n      {\n        type: 'object',\n        properties: {\n          ignoreThisExpressions: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Enabling allows use of `this.get()` and `this.set()` where you will generally know if `this` is an `Ember.Object`.',\n          },\n          ignoreNonThisExpressions: {\n            type: 'boolean',\n            default: false,\n            description:\n              'Enabling allows use of non-Ember objects like `server.get()` and `map.set()`.',\n          },\n        },\n        additionalProperties: false,\n      },\n    ],\n  },\n\n  create(context) {\n    let emberImportAliasName;\n    // Populated during VariableDeclarator traversal\n    const localModulesPresent = {};\n    const sourceCode = context.sourceCode;\n    const filename = context.filename;\n    const options = context.options[0] || {};\n    const message = 'Use get/set';\n\n    function report(node) {\n      context.report({\n        node: node.callee.property,\n        message,\n        fix(fixer) {\n          if (!emberImportAliasName) {\n            return null;\n          }\n          // this.set('foo', 3);\n          // └┬─┘ └┬┘ └───┬──┘\n          //  │    │      └─────── args\n          //  │    └────────────── method\n          //  └─────────────────── subject\n\n          const subject = sourceCode.getText(node.callee.object);\n          const method = sourceCode.getText(node.callee.property);\n          const args = node.arguments.map((a) => sourceCode.getText(a)).join(', ');\n\n          const localModule = localModulesPresent[method];\n          const replacementMethod = localModule || `${emberImportAliasName}.${method}`;\n\n          const fixedSource = `${replacementMethod}(${subject}, ${args})`;\n\n          return fixer.replaceText(node, fixedSource);\n        },\n      });\n    }\n\n    const avoidedMethods = ['get', 'set', 'getProperties', 'setProperties', 'getWithDefault'];\n\n    const testMethodsToSkip = new Set(['get', 'set']);\n\n    // Skip mirage directory\n    if (ember.isMirageDirectory(filename)) {\n      return {};\n    }\n\n    return {\n      ImportDeclaration(node) {\n        emberImportAliasName = ember.getEmberImportAliasName(node);\n      },\n\n      VariableDeclarator(node) {\n        const isEmberImported = Boolean(emberImportAliasName);\n        const sourceCode = context.sourceCode;\n        const scope = sourceCode.getScope(node);\n        const isModuleScope = scope.type === 'module';\n        if (isEmberImported && isModuleScope) {\n          // Populate localModulesPresent as a mapping of (avoided method -> local module alias)\n          for (const methodName of avoidedMethods) {\n            const destructuredAssignment = collectObjectPatternBindings(node, {\n              [emberImportAliasName]: [methodName],\n            }).pop();\n            if (destructuredAssignment) {\n              localModulesPresent[methodName] = destructuredAssignment;\n            }\n          }\n        }\n      },\n\n      CallExpression(node) {\n        const callee = node.callee;\n        const method = callee.property;\n\n        // Skip this.get() and this.set() in tests/\n        if (\n          ember.isTestFile(filename) &&\n          isThisExpression(callee.object) &&\n          isIdentifier(method) &&\n          testMethodsToSkip.has(method.name)\n        ) {\n          return;\n        }\n\n        // Skip calls made on this\n        if (options.ignoreThisExpressions && isThisExpression(callee.object)) {\n          return;\n        }\n\n        // Skip calls made on non this expression\n        if (options.ignoreNonThisExpressions && !isThisExpression(callee.object)) {\n          return;\n        }\n\n        // Skip calls made on Ember methods\n        if (isIdentifier(callee.object) && callee.object.name === emberImportAliasName) {\n          return;\n        }\n\n        if (isIdentifier(method) && avoidedMethods.includes(method.name)) {\n          report(node);\n        }\n      },\n    };\n  },\n};\n"
  },
  {
    "path": "lib/utils/computed-properties.js",
    "content": "const types = require('./types');\nconst assert = require('assert');\n\nmodule.exports = {\n  isComputedPropertyBodyArg,\n  getComputedPropertyFunctionBody,\n};\n\n/**\n * Checks whether a computed property argument is the type of node that could contain the\n * function body of the computed property (which would be passed as the last arg).\n *\n * Handles:\n * * computed('prop1', 'prop2', function() { ... })\n * * computed('prop1', 'prop2', () => { ... })\n * * computed('prop1', 'prop2', { get() { ... } })\n *\n * @param {ASTNode} node - computed property argument to check\n * @returns {boolean} whether the node could be the function body argument of a computed property\n */\nfunction isComputedPropertyBodyArg(node) {\n  return (\n    types.isFunctionExpression(node) ||\n    types.isArrowFunctionExpression(node) ||\n    types.isObjectExpression(node)\n  );\n}\n\n/**\n * Gets the function body of the computed property.\n *\n * Handles:\n * * computed('prop1', 'prop2', function() { ... })\n * * computed('prop1', 'prop2', () => { ... })\n * * computed('prop1', 'prop2', { get() { ... } })\n * * @computed('prop1', 'prop2') get fullName() { ... }\n * * @computed get fullName() { ... }\n *\n * @param {ASTNode} node - computed property CallExpression node\n * @returns {ASTNode} function body of computed property\n */\nfunction getComputedPropertyFunctionBody(node) {\n  assert(\n    types.isCallExpression(node) || types.isIdentifier(node),\n    'Should only call this function on a CallExpression or Identifier'\n  );\n\n  if (types.isCallExpression(node)) {\n    const lastArg = node.arguments.at(-1);\n\n    if (types.isArrowFunctionExpression(lastArg) || types.isFunctionExpression(lastArg)) {\n      // Example: computed('prop1', 'prop2', function() { ... })\n      // Example: computed('prop1', 'prop2', () => { ... })\n      return lastArg.body;\n    } else if (types.isObjectExpression(lastArg)) {\n      // Example: computed('prop1', 'prop2', { get() { ... } })\n      const getFunction = lastArg.properties.find(\n        (property) =>\n          property.method && types.isIdentifier(property.key) && property.key.name === 'get'\n      );\n      if (getFunction) {\n        return getFunction.value.body;\n      }\n    }\n  }\n\n  if (\n    types.isDecorator(node.parent) &&\n    types.isMethodDefinition(node.parent.parent) &&\n    node.parent.parent.kind === 'get'\n  ) {\n    // Example: @computed('first', 'last') get fullName() {}\n    // Example: @computed get fullName() {}\n    return node.parent.parent.value.body;\n  }\n\n  return undefined;\n}\n"
  },
  {
    "path": "lib/utils/computed-property-dependent-keys.js",
    "content": "'use strict';\n\nconst javascriptUtils = require('./javascript');\nconst { getName } = require('../utils/utils');\nconst types = require('../utils/types');\nconst decoratorUtils = require('../utils/decorators');\nconst assert = require('assert');\nconst { getMacrosFromImports } = require('../utils/computed-property-macros');\n\nmodule.exports = {\n  collapseKeys,\n  expandKeys,\n  expandKey,\n  findComputedPropertyDependentKeys,\n  getComputedPropertyDependentKeys,\n  computedPropertyDependencyMatchesKeyPath,\n  keyExistsAsPrefixInList,\n};\n\nfunction isBare(key) {\n  return !key.includes('.') || key.endsWith('[]');\n}\n\n/**\n * Collapse dependency keys with braces if possible.\n *\n * Example:\n * Input: [\"foo.bar\", \"foo.baz\", \"quux.[]\"]\n * Output: [\"foo.{bar,baz}\", \"quux.[]\"]\n *\n * @param {Array<string>} keys\n * @returns string\n */\nfunction collapseKeys(keys) {\n  const uniqueKeys = [...new Set(keys)];\n\n  const bareKeys = uniqueKeys.filter(isBare);\n  const rest = uniqueKeys.filter((key) => !isBare(key));\n\n  const mapByParent = rest.reduce((mapByParent, key) => {\n    const [head, ...rest] = key.split('.').reverse();\n    const parent = rest.reverse().join('.');\n\n    mapByParent.set(parent, mapByParent.get(parent) || []);\n    mapByParent.get(parent).push(head);\n\n    return mapByParent;\n  }, new Map());\n\n  const joined = [...mapByParent.keys()].map((parent) => {\n    const children = mapByParent.get(parent);\n    if (children.length > 1) {\n      return `${parent}.{${children.sort().join(',')}}`;\n    }\n    return `${parent}.${children[0]}`;\n  });\n\n  return [...bareKeys, ...joined].sort().map((key) => `'${key}'`);\n}\n\n/**\n * [\"foo.{bar,baz}\", \"quux\"] => [\"foo.bar\", \"foo.baz\", \"quux\"]\n * @param {Array<string>} keys\n * @returns {Array<string>}\n */\nfunction expandKeys(keys) {\n  return keys.flatMap(expandKey);\n}\n\n/**\n * Expand any brace usage in a dependency key.\n *\n * Example:\n * Input: \"foo.{bar,baz}\"\n * Output: [\"foo.bar\", \"foo.baz\"]\n *\n * @param {string} key\n * @returns {Array<string>}\n */\nfunction expandKey(key) {\n  if (key.includes('{')) {\n    // key = \"foo.{bar,baz}\"\n    const keyParts = key.split('{'); // [\"foo\", \"{bar,baz}\"]\n    const keyBeforeCurly = keyParts[0].slice(0, Math.max(0, keyParts[0].length - 1)); // \"foo\"\n    const keyAfterCurly = keyParts[1]; // \"{bar,baz}\"\n    const keyAfterCurlySplitByCommas = keyAfterCurly.replaceAll(/{|}/g, '').split(','); // [\"bar\", \"baz\"]\n    const keyRecombined = [[keyBeforeCurly], keyAfterCurlySplitByCommas]; // [[\"foo\"], [\"baz\", \"baz\"]]\n    return keyRecombined\n      .reduce(\n        (acc, nextParts) =>\n          // iteration 1 ([\"foo\"]): do nothing (duplicate 0 times), resulting in acc === [[\"foo\"]]\n          // iteration 2 ([\"bar\", \"baz\"]): duplicate acc once, resulting in `[[\"foo\"], [\"foo\"]]\n          javascriptUtils.duplicateArrays(acc, nextParts.length - 1).map(\n            (base, index) =>\n              // evenly distribute the parts across the repeated base keys.\n              // nextParts[0 % 2] => \"bar\"\n              // nextParts[1 % 2] => \"baz\"\n              base.concat(nextParts[index % nextParts.length]) // eslint-disable-line unicorn/prefer-spread\n          ),\n        [[]]\n      ) // [[\"foo\", \"bar\"], [\"foo\", \"baz\"]]\n      .map((expanded) => expanded.filter((part) => part !== '').join('.')); // [\"foo.bar\", \"foo.baz\"]\n  } else {\n    // No braces.\n    // Example: \"hello.world\"\n    return key;\n  }\n}\n\nconst ARRAY_PROPERTIES = new Set(['length', 'firstObject', 'lastObject']);\n\n/**\n * Determines whether a computed property dependency matches a key path.\n *\n * @param {string} dependency\n * @param {string} keyPath\n * @returns {boolean}\n */\nfunction computedPropertyDependencyMatchesKeyPath(dependency, keyPath) {\n  const dependencyParts = dependency.split('.');\n  const keyPathParts = keyPath.split('.');\n  const minLength = Math.min(dependencyParts.length, keyPathParts.length);\n\n  for (let i = 0; i < minLength; i++) {\n    const dependencyPart = dependencyParts[i];\n    const keyPathPart = keyPathParts[i];\n\n    if (dependencyPart === keyPathPart) {\n      continue;\n    }\n\n    // When dealing with arrays some keys encompass others. For example, `@each`\n    // encompasses `[]` and `length` because any `@each` is triggered on any\n    // array mutation as well as for some element property. `[]` is triggered\n    // only on array mutation and so will always be triggered when `@each` is.\n    // Similarly, `length` will always trigger if `[]` triggers and so is\n    // encompassed by it.\n    if (dependencyPart === '[]' || dependencyPart === '@each') {\n      const subordinateProperties = new Set(ARRAY_PROPERTIES);\n\n      if (dependencyPart === '@each') {\n        subordinateProperties.add('[]');\n      }\n\n      return (\n        !keyPathPart || (keyPathParts.length === i + 1 && subordinateProperties.has(keyPathPart))\n      );\n    }\n\n    return false;\n  }\n\n  // len(foo.bar.baz) > len(foo.bar), and so matches.\n  return dependencyParts.length > keyPathParts.length;\n}\n\n/**\n * Checks if the `key` is a prefix of any item in `keys`.\n *\n * Example:\n *    `keys`: `['a', 'b.c']`\n *    `key`: `'b'`\n *    Result: `true`\n *\n * @param {String[]} keys - list of dependent keys\n * @param {String} key - dependent key\n * @returns boolean\n */\nfunction keyExistsAsPrefixInList(keys, key) {\n  return keys.some((currentKey) => computedPropertyDependencyMatchesKeyPath(currentKey, key));\n}\n\n/**\n * A configuration for determining what arguments of a computed macro should be used as dependency keys\n * @typedef {Object} ComputedMacroConfiguration\n * @property {Object} [strings] - the configuration for which string arguments should be used\n * @property {number} strings.startIndex - the first index to look for string arguments\n * @property {number} strings.count - how many arguments to look at, including the start index.\n * @property {Object} [objects] - the configuration for which object arguments' values should  be used\n * @property {Object} objects.index - the argument index to look for object values in\n * @property {Object} [objects.allowedKeys] - if present, indicates which keys should be looked at for getting values.\n */\n\n/**\n * Gets the set of computed property dependency keys used inside a class.\n *\n * @param {Node} nodeClass - Node for the class\n * @param {string} [computedImportName] - The name by which computed from '@ember/object' as imported with.\n * @param {Map<String, ComputedMacroConfiguration>} - A mapping from the names by which computed macros were imported to the configuration for what keys they depend on\n * @param {Map<String, Map<String, ComputedMacroConfiguration>} - A mapping from the names by which a computed macro index was imported to a map from what properties it has to what their macro configurations are.\n * @returns {Set<string>} - set of dependent keys used inside the class\n */\nfunction findComputedPropertyDependentKeys(\n  nodeClass,\n  computedImportName,\n  macrosByName,\n  macrosByIndexName\n) {\n  if (types.isClassDeclaration(nodeClass)) {\n    // Native JS class.\n    return new Set(\n      nodeClass.body.body.flatMap((node) => {\n        // Check for `computed` itself.\n        const computedDecorator = decoratorUtils.findDecorator(node, computedImportName);\n        if (computedDecorator) {\n          return getComputedPropertyDependentKeys(computedDecorator.expression);\n        }\n\n        // Check for a computed macro.\n        const macroConfigsByName = getMacrosFromImports(macrosByName, macrosByIndexName);\n        const macroDecorator = decoratorUtils.findDecoratorByNameCallback(node, (decoratorName) =>\n          macroConfigsByName.has(decoratorName)\n        );\n        if (macroDecorator) {\n          return getComputedPropertyDependentKeys(\n            macroDecorator.expression,\n            macroConfigsByName.get(decoratorUtils.getDecoratorName(macroDecorator))\n          );\n        }\n\n        return [];\n      })\n    );\n  } else if (types.isCallExpression(nodeClass)) {\n    // Classic class.\n    return new Set(\n      nodeClass.arguments.filter(types.isObjectExpression).flatMap((classObject) => {\n        return classObject.properties.flatMap((node) => {\n          if (types.isProperty(node)) {\n            const name = getName(node.value);\n            if (!name) {\n              return [];\n            }\n\n            // Check for `computed` itself.\n            if (name === computedImportName) {\n              return getComputedPropertyDependentKeys(node.value);\n            }\n\n            // Check for a computed macro.\n            const macroConfigsByName = getMacrosFromImports(macrosByName, macrosByIndexName);\n            if (macroConfigsByName.has(name)) {\n              return getComputedPropertyDependentKeys(node.value, macroConfigsByName.get(name));\n            }\n          }\n          return [];\n        });\n      })\n    );\n  } else {\n    assert(false, 'Unexpected node type for a class.');\n  }\n\n  return new Set();\n}\n\n/**\n * Returns an array of the dependency keys for a particular argument and index for a particular configuration.\n *\n * @param {Object} argument - the argument to a computed macro\n * @param {number} index - the index of this particular argument for the computed macro\n * @param {ComputedMacroConfiguration} config - the configuration indicating how dependent keys should be gotten from the argument\n * @returns {String[]} - the list of string dependent keys indicated by the configuration.\n */\nfunction getComputedPropertyDependentKeysForConfig(argument, index, { strings, objects }) {\n  if (strings) {\n    const { startIndex, count } = strings;\n    if (\n      index >= startIndex &&\n      index < startIndex + count &&\n      argument.type === 'Literal' &&\n      typeof argument.value === 'string'\n    ) {\n      return [argument.value];\n    }\n  }\n\n  if (objects) {\n    const { index: configIndex, keys: allowedKeys } = objects;\n\n    if (index === configIndex && argument.type === 'ObjectExpression') {\n      return argument.properties\n        .filter(({ key }) => {\n          if (!allowedKeys) {\n            return true;\n          }\n\n          if (key.type === 'Identifier') {\n            return allowedKeys.includes(key.name);\n          } else if (key.type === 'Literal' && typeof key.value === 'string') {\n            return allowedKeys.includes(key.value);\n          } else {\n            return false;\n          }\n        })\n        .filter(\n          (property) =>\n            property.value.type === 'Literal' && typeof property.value.value === 'string'\n        )\n        .map((property) => property.value.value);\n    }\n  }\n\n  return [];\n}\n\n/**\n * Gets the list of string dependent keys from a computed property.\n *\n * @param {Node} node - the computed property node\n * @param {ComputedMacroConfiguration} [computedDependenciesConfig=null] - the configuration indicating how dependent keys should be gotten from the node.\n *   If not present, all string arguments will be used.\n * @returns {String[]} - the list of string dependent keys from this computed property\n */\nfunction getComputedPropertyDependentKeys(node, computedDependenciesConfig = null) {\n  if (node.type === 'ChainExpression') {\n    return getComputedPropertyDependentKeys(node.expression);\n  }\n\n  if (!node.arguments) {\n    return [];\n  }\n\n  const baseKeys = node.arguments.flatMap((argument, index) => {\n    if (!computedDependenciesConfig) {\n      return argument.type === 'Literal' && typeof argument.value === 'string'\n        ? [argument.value]\n        : [];\n    }\n\n    return computedDependenciesConfig.argumentFormat.flatMap(({ strings, objects }) => {\n      return getComputedPropertyDependentKeysForConfig(argument, index, { strings, objects });\n    });\n  });\n\n  return expandKeys(baseKeys);\n}\n"
  },
  {
    "path": "lib/utils/computed-property-macros.js",
    "content": "const assert = require('assert');\n\n/**\n * Example macros:\n * - and('x', 'y', 'z') can have any number of tracked dependent key arguments\n * - mapBy('chores', 'done', true) only has a single tracked dependent key argument\n */\nconst MACROS_TO_TRACKED_ARGUMENT_COUNT = {\n  alias: 1,\n  and: Number.MAX_VALUE,\n  bool: 1,\n  collect: Number.MAX_VALUE,\n  deprecatingAlias: 1,\n  empty: 1,\n  equal: 1,\n  filter: 1,\n  filterBy: 1,\n  gt: 1,\n  gte: 1,\n  intersect: Number.MAX_VALUE,\n  lt: 1,\n  lte: 1,\n  map: 1,\n  mapBy: 1,\n  match: 1,\n  max: 1,\n  min: 1,\n  none: 1,\n  not: 1,\n  notEmpty: 1,\n  oneWay: 1,\n  or: Number.MAX_VALUE,\n  readOnly: 1,\n  reads: 1,\n  setDiff: 2,\n  sort: 1,\n  sum: Number.MAX_VALUE,\n  union: Number.MAX_VALUE,\n  uniq: 1,\n  uniqBy: 1,\n};\n\n// Configurations for the default macros that match the configurations for the\n// no-assignment-of-untracked-properties-used-in-tracking-contexts rule\nconst DEFAULT_MACRO_CONFIGURATIONS = Object.entries(MACROS_TO_TRACKED_ARGUMENT_COUNT).map(\n  ([macroName, argumentCount]) => ({\n    name: macroName,\n    path: '@ember/object/computed',\n    indexName: 'computed',\n    indexPath: '@ember/object',\n    argumentFormat: [\n      {\n        strings: {\n          startIndex: 0,\n          count: argumentCount,\n        },\n      },\n    ],\n  })\n);\n\n/**\n * @returns {string[]} - a list of all macros.\n */\nfunction getMacros() {\n  return Object.keys(MACROS_TO_TRACKED_ARGUMENT_COUNT);\n}\n\n/**\n * Example of return value: ['and', 'readOnly', 'computed.and', 'computed.readOnly', ...]\n *\n * @param {string} computedImportName - name that computed is imported under\n * @param {set<string>} macroImportNames\n * @returns {set<string>} - a set containing all possible macro names to check for.\n */\nfunction getMacrosFromImports(macrosByImport, macrosByIndexImport) {\n  return new Map([\n    ...macrosByImport,\n    ...[...macrosByIndexImport].flatMap(([indexName, propertyConfigs]) => {\n      return [...propertyConfigs].map(([name, config]) => [`${indexName}.${name}`, config]);\n    }),\n  ]);\n}\n\n/**\n * @param {string} macro\n * @returns {number} - the number arguments that are tracked dependent keys for a given macro.\n */\nfunction getTrackedArgumentCount(macro) {\n  assert(typeof macro === 'string', 'macro parameter should be a string');\n\n  return MACROS_TO_TRACKED_ARGUMENT_COUNT[macro];\n}\n\n/**\n * @param {string} macro - imported macro name\n * @param {map<string,string>} macroImportNames\n * @returns {string} the original name of the imported macro\n */\nfunction macroToCanonicalName(macro, macroImportNames) {\n  assert(typeof macro === 'string', 'macro parameter should be a string');\n\n  return macro.includes('.')\n    ? macro.slice(macro.lastIndexOf('.') + 1) // Removes the leading `computed.`\n    : [...macroImportNames.keys()].find(\n        (canonicalName) => macroImportNames.get(canonicalName) === macro\n      );\n}\n\nmodule.exports = {\n  getMacros,\n  getMacrosFromImports,\n  getTrackedArgumentCount,\n  macroToCanonicalName,\n  DEFAULT_MACRO_CONFIGURATIONS,\n  MACROS_TO_TRACKED_ARGUMENT_COUNT,\n};\n"
  },
  {
    "path": "lib/utils/decorators.js",
    "content": "const { getName } = require('./utils');\nconst types = require('./types');\nconst assert = require('assert');\n\nmodule.exports = {\n  getDecoratorName,\n  getDecorator,\n  findDecorator,\n  findDecoratorByNameCallback,\n  hasDecorator,\n  isClassPropertyOrPropertyDefinitionWithDecorator,\n};\n\n/**\n * @param {node} node - decorator\n * @returns {string} - name of decorator (i.e. `computed`, `computed.readOnly`)\n */\nfunction getDecoratorName(node) {\n  assert(types.isDecorator(node), 'must call this function on a Decorator');\n  return getName(node.expression);\n}\n\n/**\n * Finds the decorator with the given name.\n *\n * @param {Node} node The node to check.\n * @param {string} decoratorName The decorator name to look for.\n * @returns {Node} The decorator found.\n */\nfunction findDecorator(node, decoratorName) {\n  return (\n    node.decorators &&\n    node.decorators.find((decorator) => getDecoratorName(decorator) === decoratorName)\n  );\n}\n\n/**\n * Finds the decorator that returns true for the given decorator name callback function.\n *\n * @param {Node} node The node to check.\n * @param {function} callback The callback function to check against each decorator name.\n * @returns {Node} The decorator found.\n */\nfunction findDecoratorByNameCallback(node, callback) {\n  return (\n    node.decorators && node.decorators.find((decorator) => callback(getDecoratorName(decorator)))\n  );\n}\n\n/**\n * Check whether or not a node has at least the given decorator\n *\n * @param {Object} node The node to check.\n * @param {string?} decoratorName The decorator to look for\n * @returns {boolean} Whether or not the node has the given decorator.\n */\nfunction hasDecorator(node, decoratorName) {\n  if (!node.decorators) {\n    return false;\n  }\n\n  return node.decorators.some((decorator) => getDecoratorName(decorator) === decoratorName);\n}\n\n/**\n * Get applied decorator node for the given decorator type\n *\n * @param {Object} node The node to check.\n * @param {string?} decoratorName The decorator to look for\n * @returns {Node} Node for the decorator\n */\nfunction getDecorator(node, decoratorName) {\n  if (!node.decorators) {\n    return null;\n  }\n\n  return node.decorators.find((decorator) => getDecoratorName(decorator) === decoratorName);\n}\n\n/**\n * Check whether or not a node is a ClassProperty / PropertyDefinition, with at least the given decorator.\n * If no decoratorName is provided, will return whether the node has any decorators at all\n *\n * @param {Object} node The node to check.\n * @param {string?} decoratorName The decorator to look for\n * @returns {boolean} Whether or not the node is a ClassProperty / PropertyDefinition with the given decorator.\n */\nfunction isClassPropertyOrPropertyDefinitionWithDecorator(node, decoratorName) {\n  return types.isClassPropertyOrPropertyDefinition(node) && hasDecorator(node, decoratorName);\n}\n"
  },
  {
    "path": "lib/utils/editorconfig.js",
    "content": "'use strict';\n\nconst editorconfig = require('editorconfig');\n\n/**\n * Resolve editorconfig properties for a given file path using the official\n * editorconfig library.\n *\n * Returns an object like `{ indent_size: 4, indent_style: 'space', ... }`\n * with only the properties that matched. Returns an empty object if no\n * .editorconfig is found or no sections match.\n */\nfunction resolveEditorConfig(filePath) {\n  return editorconfig.parseSync(filePath);\n}\n\nmodule.exports = { resolveEditorConfig };\n"
  },
  {
    "path": "lib/utils/ember-source-version.js",
    "content": "'use strict';\n\nconst fs = require('node:fs');\n\n/**\n * Get the installed ember-source version by resolving its package.json.\n *\n * @param {string} [projectRoot] - Project root directory (defaults to process.cwd())\n * @returns {string|null} The installed ember-source version, or null if not found\n */\nfunction getEmberSourceVersion(projectRoot) {\n  try {\n    // eslint-disable-next-line n/no-missing-require\n    const pkgPath = require.resolve('ember-source/package.json', {\n      paths: [projectRoot || process.cwd()],\n    });\n    const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\n    return pkg.version || null;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Check if a semver version string meets a minimum major.minor requirement.\n *\n * @param {string} version - Semver version string (e.g. '6.8.0')\n * @param {number} major - Required minimum major version\n * @param {number} minor - Required minimum minor version\n * @returns {boolean} True if version >= major.minor\n */\nfunction isVersionAtLeast(version, major, minor) {\n  if (!version || typeof version !== 'string') {\n    return false;\n  }\n\n  const parts = version.split('.');\n  const vMajor = Number.parseInt(parts[0], 10);\n  const vMinor = Number.parseInt(parts[1], 10);\n\n  if (Number.isNaN(vMajor) || Number.isNaN(vMinor)) {\n    return false;\n  }\n\n  return vMajor > major || (vMajor === major && vMinor >= minor);\n}\n\n/**\n * Check if the installed ember-source version meets a minimum major.minor requirement.\n *\n * @param {number} major - Required minimum major version\n * @param {number} minor - Required minimum minor version\n * @param {string} [projectRoot] - Project root directory (defaults to process.cwd())\n * @returns {boolean} True if installed ember-source version >= major.minor\n */\nfunction isEmberSourceVersionAtLeast(major, minor, projectRoot) {\n  const version = getEmberSourceVersion(projectRoot);\n  return isVersionAtLeast(version, major, minor);\n}\n\nmodule.exports = {\n  isEmberSourceVersionAtLeast,\n};\n"
  },
  {
    "path": "lib/utils/ember.js",
    "content": "'use strict';\n\nconst path = require('path');\nconst utils = require('./utils');\nconst importUtils = require('./import');\nconst types = require('./types');\nconst assert = require('assert');\nconst decoratorUtils = require('../utils/decorators');\nconst { getNodeOrNodeFromVariable } = require('../utils/utils');\nconst kebabCase = require('lodash.kebabcase');\n\nmodule.exports = {\n  isDSModel,\n  isEmberDataModel,\n  isModule,\n  isModuleByFilePath,\n  isMirageDirectory,\n  isMirageConfig,\n  isTestFile,\n\n  isEmberCoreModule,\n  isAnyEmberCoreModule,\n  isEmberComponent,\n  isGlimmerComponent,\n  isEmberController,\n  isEmberMixin,\n  isEmberRoute,\n  isEmberService,\n  isEmberArrayProxy,\n  isEmberObjectProxy,\n  isEmberObject,\n  isEmberHelper,\n  isEmberProxy,\n\n  isSingleLineFn,\n  isMultiLineFn,\n  isFunctionExpression,\n\n  getModuleProperties,\n  getEmberImportAliasName,\n\n  isInjectedServiceProp,\n  isInjectedControllerProp,\n  isObserverProp,\n  isObjectProp,\n  isArrayProp,\n  isComputedProp,\n  isCustomProp,\n  isActionsProp,\n  isRouteLifecycleHook,\n\n  isRelation,\n\n  isComponentLifecycleHook,\n  isGlimmerComponentLifecycleHook,\n\n  isRoute,\n  isRouteDefaultProp,\n\n  isControllerDefaultProp,\n  parseDependentKeys,\n  unwrapBraceExpressions,\n  hasDuplicateDependentKeys,\n\n  isExtendObject,\n  isReopenObject,\n  isReopenClassObject,\n\n  isEmberObjectImplementingUnknownProperty,\n\n  isObserverDecorator,\n\n  convertServiceNameToKebabCase,\n};\n\n// Private\nconst CORE_MODULE_IMPORT_PATHS = {\n  Component: '@ember/component',\n  GlimmerComponent: '@glimmer/component',\n  Controller: '@ember/controller',\n  Mixin: '@ember/object/mixin',\n  Route: '@ember/routing/route',\n  Service: '@ember/service',\n  ArrayProxy: '@ember/array/proxy',\n  ObjectProxy: '@ember/object/proxy',\n  EmberObject: '@ember/object',\n  Helper: '@ember/component/helper',\n};\n\nfunction isClassicEmberCoreModule(node, module, filePath) {\n  const isExtended = isExtendObject(node);\n  let isModuleByPath;\n\n  if (filePath) {\n    isModuleByPath = isModuleByFilePath(filePath, module.toLowerCase()) && isExtended;\n  }\n\n  return isModule(node, module) || isModuleByPath;\n}\n\nfunction isLocalModule(callee, element) {\n  return (\n    (types.isIdentifier(callee) && callee.name === element) ||\n    (types.isIdentifier(callee.object) && callee.object.name === element)\n  );\n}\n\nfunction isEmberModule(callee, element, module) {\n  const memberExp = types.isMemberExpression(callee.object) ? callee.object : callee;\n\n  return (\n    isLocalModule(memberExp, module) &&\n    types.isIdentifier(memberExp.property) &&\n    memberExp.property.name === element\n  );\n}\n\nfunction isPropOfType(node, type) {\n  if (types.isProperty(node) && types.isCallExpression(node.value)) {\n    const calleeNode = node.value.callee;\n    return types.isIdentifier(calleeNode) && calleeNode.name === type;\n  } else if (\n    (types.isClassPropertyOrPropertyDefinition(node) || types.isMethodDefinition(node)) &&\n    node.decorators\n  ) {\n    return node.decorators.some((decorator) => {\n      const expression = decorator.expression;\n      return (\n        (types.isIdentifier(expression) && expression.name === type) ||\n        (types.isCallExpression(expression) && expression.callee.name === type)\n      );\n    });\n  }\n  return false;\n}\n\n// Public\n\nfunction isModule(node, element, moduleName = 'Ember') {\n  if (types.isIdentifier(node)) {\n    return isLocalModule(node, element) || isEmberModule(node, element, moduleName);\n  }\n\n  if (types.isCallExpression(node)) {\n    return isLocalModule(node.callee, element) || isEmberModule(node.callee, element, moduleName);\n  }\n\n  return false;\n}\n\nfunction isDSModel(node, filePath) {\n  const isExtended = isExtendObject(node);\n  let isModuleByPath = false;\n\n  if (filePath && isExtended) {\n    isModuleByPath = isModuleByFilePath(filePath, 'model');\n  }\n\n  return isModule(node, 'Model', 'DS') || isModuleByPath;\n}\n\nfunction isEmberDataModel(context, node) {\n  if (!(types.isClassDeclaration(node) || node.type === 'ClassExpression')) {\n    assert(\n      false,\n      'Function should only be called on a `ClassDeclaration`/`ClassExpression` (native class)'\n    );\n  }\n\n  if (!node.superClass) {\n    return false;\n  }\n\n  if (types.isIdentifier(node.superClass)) {\n    const superClassImportPath = importUtils.getSourceModuleNameForIdentifier(\n      context,\n      node.superClass\n    );\n\n    if (\n      superClassImportPath === '@ember-data/model' ||\n      superClassImportPath === 'ember-data/model'\n    ) {\n      return true;\n    }\n  }\n\n  if (\n    types.isCallExpression(node.superClass) &&\n    types.isMemberExpression(node.superClass.callee) &&\n    types.isIdentifier(node.superClass.callee.property) &&\n    node.superClass.callee.property.name === 'extend' &&\n    types.isIdentifier(node.superClass.callee.object)\n  ) {\n    const superClassImportPath = importUtils.getSourceModuleNameForIdentifier(\n      context,\n      node.superClass.callee.object\n    );\n\n    if (\n      superClassImportPath === '@ember-data/model' ||\n      superClassImportPath === 'ember-data/model'\n    ) {\n      return true;\n    }\n  }\n\n  return isModuleByFilePath(context.filename, 'model');\n}\n\nfunction isModuleByFilePath(filePath, module) {\n  const expectedFileNameJs = `${module}.js`;\n  const expectedFileNameTs = `${module}.ts`;\n  const expectedFolderName = `${module}s`;\n\n  const normalized = path.normalize(filePath);\n  const actualFolders = path.dirname(normalized).split(path.sep);\n  const actualFileName = path.basename(normalized);\n\n  /* Check both folder and filename to support both classic and POD's structure */\n  return (\n    actualFileName === expectedFileNameJs ||\n    actualFileName === expectedFileNameTs ||\n    actualFolders.includes(expectedFolderName)\n  );\n}\n\nconst validFileExtensions = ['js', 'ts', 'gjs', 'gts'];\nconst validTestFilePatterns = ['-test', '_test', '.test'];\n\nfunction isTestFile(fileName) {\n  return validFileExtensions.some((ext) =>\n    validTestFilePatterns.some((testFilePattern) => fileName.endsWith(`${testFilePattern}.${ext}`))\n  );\n}\n\nfunction isMirageDirectory(fileName) {\n  const pathParts = path.normalize(fileName).split(path.sep);\n  return pathParts.includes('mirage');\n}\n\nfunction isMirageConfig(fileName) {\n  return path.normalize(fileName).endsWith(path.join('mirage', 'config.js'));\n}\n\n// Some third-party libraries have an `extend` function and we want to avoid mistaking this for an extended Ember object.\n// TODO: ideally, this would check the actual name that these libraries are imported under, but there's a lot of plumbing needed for that.\nconst COMMON_JQUERY_NAMES = ['$', 'jQuery']; // https://api.jquery.com/jquery.extend/\nconst COMMON_LODASH_NAMES = ['_', 'lodash']; // https://lodash.com/docs/4.17.15#assignIn\nconst THIRD_PARTY_LIBRARIES_WITH_EXTEND = new Set([...COMMON_JQUERY_NAMES, ...COMMON_LODASH_NAMES]);\n\nfunction isExtendObject(node) {\n  // Check for:\n  // * foo.extend();\n  // * foo['extend']();\n  return (\n    node.type === 'CallExpression' &&\n    node.callee.type === 'MemberExpression' &&\n    ((node.callee.property.type === 'Identifier' && node.callee.property.name === 'extend') ||\n      (node.callee.property.type === 'Literal' && node.callee.property.value === 'extend')) &&\n    !(\n      node.callee.object.type === 'Identifier' &&\n      THIRD_PARTY_LIBRARIES_WITH_EXTEND.has(node.callee.object.name)\n    )\n  );\n}\n\nfunction isReopenClassObject(node) {\n  return node.callee.property && node.callee.property.name === 'reopenClass';\n}\n\nfunction isReopenObject(node) {\n  return node.callee.property && node.callee.property.name === 'reopen';\n}\n\nfunction isEmberCoreModule(context, node, moduleName) {\n  if (types.isCallExpression(node)) {\n    // \"classic\" class pattern\n    return isClassicEmberCoreModule(node, moduleName, context.filename);\n  } else if (types.isClassDeclaration(node) || node.type === 'ClassExpression') {\n    // native classes\n    if (\n      // class Foo extends Component\n      !node.superClass ||\n      // class Foo extends Component.extend(SomeMixin)\n      !(\n        types.isIdentifier(node.superClass) ||\n        (types.isCallExpression(node.superClass) &&\n          types.isMemberExpression(node.superClass.callee) &&\n          types.isIdentifier(node.superClass.callee.property) &&\n          node.superClass.callee.property.name === 'extend')\n      )\n    ) {\n      return false;\n    }\n\n    const superClass = types.isIdentifier(node.superClass)\n      ? node.superClass\n      : node.superClass.callee.object;\n\n    const superClassImportPath = importUtils.getSourceModuleNameForIdentifier(context, superClass);\n\n    if (superClassImportPath === CORE_MODULE_IMPORT_PATHS[moduleName]) {\n      return true;\n    }\n  } else {\n    assert(\n      false,\n      'Function should only be called on a `CallExpression` (classic class) or `ClassDeclaration`/`ClassExpression` (native class)'\n    );\n  }\n  return false;\n}\n\nfunction isAnyEmberCoreModule(context, node) {\n  return (\n    isEmberComponent(context, node) ||\n    isGlimmerComponent(context, node) ||\n    isEmberController(context, node) ||\n    isEmberMixin(context, node) ||\n    isEmberRoute(context, node) ||\n    isEmberService(context, node) ||\n    isEmberArrayProxy(context, node) ||\n    isEmberObjectProxy(context, node) ||\n    isEmberObject(context, node) ||\n    isEmberHelper(context, node)\n  );\n}\n\nfunction isEmberComponent(context, node) {\n  return isEmberCoreModule(context, node, 'Component');\n}\n\nfunction isGlimmerComponent(context, node) {\n  return isEmberCoreModule(context, node, 'GlimmerComponent');\n}\n\nfunction isEmberController(context, node) {\n  return isEmberCoreModule(context, node, 'Controller');\n}\n\nfunction isEmberMixin(context, node) {\n  return isEmberCoreModule(context, node, 'Mixin');\n}\n\nfunction isEmberRoute(context, node) {\n  return isEmberCoreModule(context, node, 'Route');\n}\n\nfunction isEmberService(context, node) {\n  return isEmberCoreModule(context, node, 'Service');\n}\n\nfunction isEmberArrayProxy(context, node) {\n  return isEmberCoreModule(context, node, 'ArrayProxy');\n}\n\nfunction isEmberObjectProxy(context, node) {\n  return isEmberCoreModule(context, node, 'ObjectProxy');\n}\n\nfunction isEmberObject(context, node) {\n  return isEmberCoreModule(context, node, 'EmberObject');\n}\n\nfunction isEmberHelper(context, node) {\n  return isEmberCoreModule(context, node, 'Helper');\n}\n\nfunction isEmberProxy(context, node) {\n  return isEmberArrayProxy(context, node) || isEmberObjectProxy(context, node);\n}\n\n/**\n * Checks if a node is a service injection. Looks for:\n * * service()\n * * Ember.inject.service()\n * @param {node} node\n * @param {string} importedEmberName name that `Ember` is imported under\n * @param {string} importedInjectName name that `inject` is imported under\n * @returns\n */\nfunction isInjectedServiceProp(node, importedEmberName, importedInjectName) {\n  return (\n    isPropOfType(node, importedInjectName) ||\n    (types.isProperty(node) &&\n      types.isCallExpression(node.value) &&\n      types.isMemberExpression(node.value.callee) &&\n      types.isMemberExpression(node.value.callee.object) &&\n      types.isIdentifier(node.value.callee.object.object) &&\n      node.value.callee.object.object.name === importedEmberName &&\n      types.isIdentifier(node.value.callee.object.property) &&\n      node.value.callee.object.property.name === 'inject' &&\n      types.isIdentifier(node.value.callee.property) &&\n      node.value.callee.property.name === 'service')\n  );\n}\n\n/**\n * Checks if a node is a controller injection. Looks for:\n * * controller()\n * * Ember.inject.controller()\n * @param {node} node\n * @param {string} importedEmberName name that `Ember` is imported under\n * @param {string} importedControllerName name that `controller` is imported under\n * @returns\n */\nfunction isInjectedControllerProp(node, importedEmberName, importedControllerName) {\n  return (\n    isPropOfType(node, importedControllerName) ||\n    (types.isProperty(node) &&\n      types.isCallExpression(node.value) &&\n      types.isMemberExpression(node.value.callee) &&\n      types.isMemberExpression(node.value.callee.object) &&\n      types.isIdentifier(node.value.callee.object.object) &&\n      node.value.callee.object.object.name === importedEmberName &&\n      types.isIdentifier(node.value.callee.object.property) &&\n      node.value.callee.object.property.name === 'inject' &&\n      types.isIdentifier(node.value.callee.property) &&\n      node.value.callee.property.name === 'controller')\n  );\n}\n\n/**\n * Checks if a node is an observer prop. Looks for:\n * * observer()\n * * Ember.observer()\n * @param {node} node\n * @param {string} importedEmberName name that `Ember` is imported under\n * @param {string} importedObserverName name that `observer` is imported under\n * @returns\n */\nfunction isObserverProp(node, importedEmberName, importedObserverName) {\n  return (\n    isPropOfType(node, importedObserverName) ||\n    (types.isProperty(node) &&\n      types.isCallExpression(node.value) &&\n      types.isMemberExpression(node.value.callee) &&\n      types.isIdentifier(node.value.callee.object) &&\n      node.value.callee.object.name === importedEmberName &&\n      types.isIdentifier(node.value.callee.property) &&\n      types.isIdentifier(node.value.callee.property) &&\n      node.value.callee.property.name === 'observer')\n  );\n}\n\nconst allowedMemberExpNames = new Set(['volatile', 'readOnly', 'property', 'meta']);\n/**\n * Checks if a node is a computed property.\n * @param {node} node\n * @param {string} importedEmberName name that `Ember` is imported under\n * @param {string} importedComputedName name that `computed` is imported under\n * @param {object} options\n * @param {boolean} options.includeSuffix whether to consider something like computed().volatile() as a computed property\n * @param {boolean} options.includeMacro whether to consider something like computed.and() as a computed property\n */\nfunction isComputedProp(\n  node,\n  importedEmberName,\n  importedComputedName,\n  { includeSuffix, includeMacro } = {}\n) {\n  return (\n    // computed\n    (types.isIdentifier(node) && node.name === importedComputedName) ||\n    // computed()\n    (types.isCallExpression(node) &&\n      types.isIdentifier(node.callee) &&\n      node.callee.name === importedComputedName) ||\n    // Ember.computed()\n    (types.isCallExpression(node) &&\n      types.isMemberExpression(node.callee) &&\n      types.isIdentifier(node.callee.object) &&\n      node.callee.object.name === importedEmberName &&\n      types.isIdentifier(node.callee.property) &&\n      node.callee.property.name === 'computed') ||\n    // Ember.computed().volatile() or computed().volatile()\n    (includeSuffix &&\n      types.isCallExpression(node) &&\n      types.isMemberExpression(node.callee) &&\n      types.isIdentifier(node.callee.property) &&\n      allowedMemberExpNames.has(node.callee.property.name) &&\n      isComputedProp(node.callee.object, importedEmberName, importedComputedName)) ||\n    (includeMacro && isComputedPropMacro(node, importedEmberName, importedComputedName))\n  );\n}\n\n/**\n * Checks if a node is a computed property macro such as: computed.and()\n * @param {node} node\n * @param {string} importedEmberName name that `Ember` is imported under\n * @param {string} importedComputedName name that `computed` is imported under\n */\nfunction isComputedPropMacro(node, importedEmberName, importedComputedName) {\n  return (\n    // computed.someMacro()\n    (types.isCallExpression(node) &&\n      types.isMemberExpression(node.callee) &&\n      types.isIdentifier(node.callee.object) &&\n      node.callee.object.name === importedComputedName &&\n      types.isIdentifier(node.callee.property)) ||\n    // Ember.computed.someMacro()\n    (types.isCallExpression(node) &&\n      types.isMemberExpression(node.callee) &&\n      types.isMemberExpression(node.callee.object) &&\n      types.isIdentifier(node.callee.object.object) &&\n      node.callee.object.object.name === importedEmberName &&\n      types.isIdentifier(node.callee.object.property) &&\n      node.callee.object.property.name === 'computed')\n  );\n}\n\nfunction isArrayProp(node) {\n  if (types.isNewExpression(node.value)) {\n    const parsedCallee = utils.parseCallee(node.value);\n    return parsedCallee.pop() === 'A';\n  }\n\n  return types.isArrayExpression(node.value);\n}\n\nfunction isObjectProp(node) {\n  if (types.isNewExpression(node.value)) {\n    const parsedCallee = utils.parseCallee(node.value);\n    return parsedCallee.pop() === 'Object';\n  }\n\n  return types.isObjectExpression(node.value);\n}\n\nfunction isCustomProp(property) {\n  const value = property.value;\n  if (!value) {\n    return false;\n  }\n  const isCustomObjectProp = types.isObjectExpression(value) && property.key.name !== 'actions';\n\n  return (\n    types.isLiteral(value) ||\n    types.isTemplateLiteral(value) ||\n    types.isIdentifier(value) ||\n    types.isArrayExpression(value) ||\n    types.isUnaryExpression(value) ||\n    isCustomObjectProp ||\n    types.isConditionalExpression(value) ||\n    types.isTaggedTemplateExpression(value)\n  );\n}\n\nfunction isRouteLifecycleHook(property) {\n  return isFunctionExpression(property.value) && isRouteLifecycleHookName(property.key.name);\n}\n\nfunction isActionsProp(property) {\n  return (\n    types.isIdentifier(property.key) &&\n    property.key.name === 'actions' &&\n    property.value &&\n    types.isObjectExpression(property.value)\n  );\n}\n\nfunction isComponentLifecycleHookName(name) {\n  return [\n    'didDestroyElement',\n    'didInsertElement',\n    'didReceiveAttrs',\n    'didRender',\n    'didUpdate',\n    'didUpdateAttrs',\n    'willClearRender',\n    'willDestroy',\n    'willDestroyElement',\n    'willInsertElement',\n    'willRender',\n    'willUpdate',\n  ].includes(name);\n}\n\nfunction isGlimmerComponentLifecycleHookName(name) {\n  return ['willDestroy'].includes(name);\n}\n\nfunction isComponentLifecycleHook(property) {\n  return isFunctionExpression(property.value) && isComponentLifecycleHookName(property.key.name);\n}\n\nfunction isGlimmerComponentLifecycleHook(property) {\n  return (\n    isFunctionExpression(property.value) && isGlimmerComponentLifecycleHookName(property.key.name)\n  );\n}\n\nfunction isRoute(node) {\n  return (\n    types.isMemberExpression(node.callee) &&\n    types.isThisExpression(node.callee.object) &&\n    types.isIdentifier(node.callee.property) &&\n    node.callee.property.name === 'route'\n  );\n}\n\nfunction isRouteLifecycleHookName(name) {\n  return [\n    'activate',\n    'afterModel',\n    'beforeModel',\n    'deactivate',\n    'model',\n    'redirect',\n    'renderTemplate',\n    'resetController',\n    'serialize',\n    'setupController',\n  ].includes(name);\n}\n\nfunction isRouteProperty(name) {\n  return [\n    'actions',\n    'concatenatedProperties',\n    'controller',\n    'controllerName',\n    'isDestroyed',\n    'isDestroying',\n    'mergedProperties',\n    'queryParams',\n    'routeName',\n    'templateName',\n  ].includes(name);\n}\n\nfunction isRouteDefaultProp(property) {\n  const isRouteClassField =\n    types.isClassPropertyOrPropertyDefinition(property) &&\n    types.isIdentifier(property.key) &&\n    isRouteProperty(property.key.name) &&\n    property.key.name !== 'actions';\n\n  return (\n    isRouteClassField ||\n    (types.isProperty(property) &&\n      types.isIdentifier(property.key) &&\n      isRouteProperty(property.key.name) &&\n      property.key.name !== 'actions')\n  );\n}\n\nfunction isControllerProperty(name) {\n  return [\n    'actions',\n    'concatenatedProperties',\n    'isDestroyed',\n    'isDestroying',\n    'mergedProperties',\n    'model',\n    'queryParams',\n    'target',\n  ].includes(name);\n}\n\nfunction isControllerDefaultProp(property) {\n  return (\n    types.isProperty(property) &&\n    types.isIdentifier(property.key) &&\n    isControllerProperty(property.key.name) &&\n    property.key.name !== 'actions'\n  );\n}\n\nfunction getModuleProperties(moduleNode, scopeManager) {\n  return moduleNode.arguments.flatMap((arg) => {\n    const resultingNode = getNodeOrNodeFromVariable(arg, scopeManager);\n    return resultingNode && resultingNode.type === 'ObjectExpression'\n      ? resultingNode.properties\n      : [];\n  });\n}\n\n/**\n * Get alias name of default ember import.\n *\n * @param  {ImportDeclaration} importDeclaration node to parse\n * @return {String}            import name\n */\nfunction getEmberImportAliasName(importDeclaration) {\n  if (!importDeclaration.source) {\n    return null;\n  }\n  if (importDeclaration.source.value !== 'ember') {\n    return null;\n  }\n  return importDeclaration.specifiers[0].local.name;\n}\n\nfunction isSingleLineFn(property, importedEmberName, importedObserverName) {\n  return (\n    (types.isMethodDefinition(property) && utils.getSize(property) === 1) ||\n    (property.value &&\n      types.isCallExpression(property.value) &&\n      utils.getSize(property.value) === 1 &&\n      !isObserverProp(property, importedEmberName, importedObserverName) &&\n      (isComputedProp(property.value, importedEmberName, 'computed', { includeSuffix: true }) ||\n        !types.isCallWithFunctionExpression(property.value)))\n  );\n}\n\nfunction isMultiLineFn(property, importedEmberName, importedObserverName) {\n  return (\n    (types.isMethodDefinition(property) && utils.getSize(property) > 1) ||\n    (property.value &&\n      types.isCallExpression(property.value) &&\n      utils.getSize(property.value) > 1 &&\n      !isObserverProp(property, importedEmberName, importedObserverName) &&\n      (isComputedProp(property.value, importedEmberName, 'computed', { includeSuffix: true }) ||\n        !types.isCallWithFunctionExpression(property.value)))\n  );\n}\n\nfunction isFunctionExpression(property) {\n  if (!property) {\n    return false;\n  }\n\n  return (\n    types.isFunctionExpression(property) ||\n    types.isArrowFunctionExpression(property) ||\n    types.isCallWithFunctionExpression(property)\n  );\n}\n\nfunction isRelation(property) {\n  const relationAttrs = ['hasMany', 'belongsTo'];\n\n  return relationAttrs.some((relation) => {\n    return (\n      (property.value && isModule(property.value, relation, 'DS')) ||\n      decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(property, relation)\n    );\n  });\n}\n\n/**\n * Checks whether a computed property has duplicate dependent keys.\n *\n * @param  {CallExpression} callExp Given call expression\n * @return {Boolean}        Flag whether dependent keys present.\n */\nfunction hasDuplicateDependentKeys(callExp, importedEmberName, importedComputedName) {\n  if (!isComputedProp(callExp, importedEmberName, importedComputedName)) {\n    return false;\n  }\n\n  const dependentKeys = parseDependentKeys(callExp);\n  const uniqueKeys = dependentKeys.filter((val, index, self) => self.indexOf(val) === index);\n\n  return uniqueKeys.length !== dependentKeys.length;\n}\n\n/**\n * Parses dependent keys from call expression and returns them in an array.\n *\n * It also unwraps the expressions, so that `model.{foo,bar}` becomes `model.foo, model.bar`.\n *\n * @param  {CallExpression} callExp CallExpression to examine\n * @return {String[]}       Array of unwrapped dependent keys\n */\nfunction parseDependentKeys(callExp) {\n  // Check whether we have a MemberExpression, eg. computed(...).volatile()\n  const isMemberExpCallExp =\n    callExp.arguments.length === 0 &&\n    types.isMemberExpression(callExp.callee) &&\n    types.isCallExpression(callExp.callee.object);\n\n  const args = isMemberExpCallExp ? callExp.callee.object.arguments : callExp.arguments;\n\n  const dependentKeys = args.filter((arg) => types.isLiteral(arg)).map((literal) => literal.value);\n\n  return unwrapBraceExpressions(dependentKeys);\n}\n\n/**\n * Unwraps brace expressions.\n *\n * @param  {String[]} dependentKeys array of strings containing unprocessed dependent keys.\n * @return {String[]} Array of unwrapped dependent keys\n */\nfunction unwrapBraceExpressions(dependentKeys) {\n  const braceExpressionRegexp = /{.+}/g;\n\n  const unwrappedExpressions = dependentKeys.map((key) => {\n    if (typeof key !== 'string' || !braceExpressionRegexp.test(key)) {\n      return key;\n    }\n\n    const braceExpansionPart = key.match(braceExpressionRegexp)[0];\n    const prefix = key.replace(braceExpansionPart, '');\n    const properties = braceExpansionPart.replace('{', '').replace('}', '').split(',');\n\n    return properties.map((property) => `${prefix}${property}`);\n  });\n\n  return unwrappedExpressions.flat();\n}\n\nfunction isEmberObjectImplementingUnknownProperty(node, scopeManager) {\n  if (types.isCallExpression(node)) {\n    if (!isExtendObject(node) && !isReopenObject(node)) {\n      return false;\n    }\n    // Classic class.\n    const properties = getModuleProperties(node, scopeManager);\n    return properties.some(\n      (property) => types.isIdentifier(property.key) && property.key.name === 'unknownProperty'\n    );\n  } else if (types.isClassDeclaration(node)) {\n    // Native class.\n    return node.body.body.some(\n      (n) =>\n        types.isMethodDefinition(n) && types.isIdentifier(n.key) && n.key.name === 'unknownProperty'\n    );\n  } else {\n    assert(\n      false,\n      'Function should only be called on a `CallExpression` (classic class) or `ClassDeclaration` (native class)'\n    );\n  }\n  return false;\n}\n\nfunction isObserverDecorator(node, importedObservesName) {\n  assert(types.isDecorator(node), 'Should only call this function on a Decorator');\n  return (\n    types.isDecorator(node) &&\n    types.isCallExpression(node.expression) &&\n    types.isIdentifier(node.expression.callee) &&\n    node.expression.callee.name === importedObservesName\n  );\n}\n\nfunction convertServiceNameToKebabCase(serviceName) {\n  return serviceName.split('/').map(kebabCase).join('/'); // splitting is used to avoid converting folder/ to folder-\n}\n"
  },
  {
    "path": "lib/utils/fixer.js",
    "content": "'use strict';\n\nconst types = require('./types');\n\nmodule.exports = {\n  insertImportDeclaration,\n  removeCommaSeparatedNode,\n};\n\nfunction removeCommaSeparatedNode(node, sourceCode, fixer) {\n  const tokenBefore = sourceCode.getTokenBefore(node);\n  const tokenAfter = sourceCode.getTokenAfter(node);\n\n  const removeComma = types.isCommaToken(tokenAfter)\n    ? fixer.remove(tokenAfter)\n    : fixer.remove(tokenBefore);\n  const removeNode = fixer.remove(node);\n\n  return [removeComma, removeNode];\n}\n\n/**\n * Returns an ESLint fixing object that inserts an import declaration depending on the passed arguments\n *\n * @param {Object} sourceCode The ESLint source code object\n * @param {Object} fixer The ESLint fixer object which will be used to apply fixes.\n * @param {String} source The name of the package from where the methods/properties will be imported.\n * @param {String} specifier The name of the method/property that needs to be imported.\n * @param {String} defaultSpecifier The name of the default specifier that needs to be imported.\n * @returns {Object} ESLint fixing object\n */\nfunction insertImportDeclaration(sourceCode, fixer, source, specifier, defaultSpecifier) {\n  return fixer.insertTextBefore(\n    sourceCode.ast,\n    `import ${defaultSpecifier ? `${defaultSpecifier}, ` : ''}{ ${specifier} } from '${source}';\\n`\n  );\n}\n"
  },
  {
    "path": "lib/utils/html-interactive-content.js",
    "content": "'use strict';\n\n/**\n * HTML \"interactive content\" classification, authoritative per\n * [HTML Living Standard §3.2.5.2.7 Interactive content]\n * (https://html.spec.whatwg.org/multipage/dom.html#interactive-content):\n *\n *   a (if the href attribute is present), audio (if the controls attribute\n *   is present), button, details, embed, iframe, img (if the usemap\n *   attribute is present), input (if the type attribute is not in the\n *   Hidden state), label, select, textarea, video (if the controls\n *   attribute is present).\n *\n * Plus <summary>, which is not in §3.2.5.2.7 but is keyboard-activatable per\n * [§4.11.2 The summary element](https://html.spec.whatwg.org/multipage/interactive-elements.html#the-summary-element).\n *\n * This is the HTML-content-model authority — it answers \"does the HTML spec\n * prohibit nesting this inside an interactive parent?\" It does NOT answer\n * \"is this an ARIA widget for AT semantics?\" (see `interactive-roles.js`\n * for that). The two questions diverge on rows like <label> (HTML: yes;\n * ARIA: structure role), <canvas> (HTML: no; ARIA: widget per axobject),\n * and <option>/<datalist> (HTML: no; ARIA: widgets). Rules that need\n * \"interactive for any reason\" should compose both authorities.\n */\n\nconst UNCONDITIONAL_INTERACTIVE_TAGS = new Set([\n  'button',\n  'details',\n  'embed',\n  'iframe',\n  'label',\n  'select',\n  'summary',\n  'textarea',\n]);\n\n/**\n * Determine whether a Glimmer element node is HTML-interactive content per\n * §3.2.5.2.7 (+ summary).\n *\n * @param {object} node                 Glimmer ElementNode (has a string `tag`).\n * @param {Function} getTextAttrValue   Helper (node, attrName) -> string | undefined\n *                                      returning the static text value of an\n *                                      attribute, or undefined for dynamic / missing.\n * @param {object} [options]\n * @param {boolean} [options.ignoreUsemap=false]  Treat `<img usemap>` as NOT interactive.\n *                                                Consumed by rules with an `ignoreUsemap`\n *                                                config option that lets authors opt out\n *                                                of image-map-based interactivity.\n * @returns {boolean}\n */\nfunction isHtmlInteractiveContent(node, getTextAttrValue, options = {}) {\n  const rawTag = node && node.tag;\n  if (typeof rawTag !== 'string' || rawTag.length === 0) {\n    return false;\n  }\n  const tag = rawTag.toLowerCase();\n\n  if (UNCONDITIONAL_INTERACTIVE_TAGS.has(tag)) {\n    return true;\n  }\n\n  // input — interactive unless type=\"hidden\"\n  if (tag === 'input') {\n    const type = getTextAttrValue(node, 'type');\n    return type === undefined || type === null || type.trim().toLowerCase() !== 'hidden';\n  }\n\n  // a — interactive only when href is present\n  if (tag === 'a') {\n    return hasAttribute(node, 'href');\n  }\n\n  // img — interactive only when usemap is present (image map)\n  if (tag === 'img') {\n    if (options.ignoreUsemap) {\n      return false;\n    }\n    return hasAttribute(node, 'usemap');\n  }\n\n  // audio / video — interactive only when controls is present\n  if (tag === 'audio' || tag === 'video') {\n    return hasAttribute(node, 'controls');\n  }\n\n  return false;\n}\n\nfunction hasAttribute(node, name) {\n  return Boolean(node.attributes && node.attributes.some((a) => a.name === name));\n}\n\nmodule.exports = { isHtmlInteractiveContent };\n"
  },
  {
    "path": "lib/utils/import.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst {\n  isCallExpression,\n  isIdentifier,\n  isImportDeclaration,\n  isMemberExpression,\n} = require('../utils/types');\n\nmodule.exports = {\n  getSourceModuleNameForIdentifier,\n  getSourceModuleName,\n  getImportIdentifier,\n};\n\n/**\n * Gets the name of the module that an identifier was imported from,\n * if it was imported\n *\n * @param {Object} context the context of the ESLint rule\n * @param {node} node the Identifier to find an import for\n * @returns {string | undefined} The name of the module the identifier was imported from, if it was imported\n */\nfunction getSourceModuleNameForIdentifier(context, node) {\n  const { ast } = context.sourceCode;\n  const sourceModuleName = getSourceModuleName(node);\n  const importDeclaration = ast.body\n    .filter(isImportDeclaration)\n    .find((importDeclaration) =>\n      importDeclaration.specifiers.some((specifier) => specifier.local.name === sourceModuleName)\n    );\n\n  return importDeclaration ? importDeclaration.source.value : undefined;\n}\n\nfunction getSourceModuleName(node) {\n  if (isCallExpression(node) && node.callee) {\n    return getSourceModuleName(node.callee);\n  } else if (isMemberExpression(node) && node.object) {\n    return getSourceModuleName(node.object);\n  } else if (isIdentifier(node)) {\n    return node.name;\n  } else {\n    assert(\n      false,\n      '`getSourceModuleName` should only be called on a `CallExpression`, `MemberExpression` or `Identifier`'\n    );\n    return undefined;\n  }\n}\n\n/**\n * Gets an import identifier (either imported or local name) from the specified ImportDeclaration.\n *\n * @param {node} node the ImportDeclaration to find the import identifier for\n * @param {string} source the source, or module name string, of the import\n * @param {string} [namedImportIdentifier=null] the named import identifier to find (will return the alias of the import, of found)\n * @returns {null} if no import is found with that name\n */\nfunction getImportIdentifier(node, source, namedImportIdentifier = null) {\n  assert(\n    isImportDeclaration(node),\n    `getImportIdentifier should be called with a node that's type is 'ImportDeclaration'. You passed '${node.type}'`\n  );\n\n  if (node.source.value !== source) {\n    return null;\n  }\n\n  return node.specifiers\n    .filter((specifier) => {\n      return (\n        (specifier.type === 'ImportSpecifier' &&\n          specifier.imported.name === namedImportIdentifier) ||\n        (!namedImportIdentifier && specifier.type === 'ImportDefaultSpecifier')\n      );\n    })\n    .map((specifier) => specifier.local.name)\n    .pop();\n}\n"
  },
  {
    "path": "lib/utils/interactive-roles.js",
    "content": "'use strict';\n\nconst { roles } = require('aria-query');\n\n// Interactive ARIA roles — concrete roles whose taxonomy descends from `widget`\n// in aria-query. This is the same derivation jsx-a11y uses (the canonical\n// peer-plugin behavior here):\n//   https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/util/isInteractiveRole.js\n//\n// Authority: aria-query widget taxonomy, one manual addition, one manual\n// exclusion. Both documented below.\n//\n// `toolbar` is added explicitly — it does not descend from `widget` per\n// aria-query's taxonomy (its superClass is structure-based), but supports\n// `aria-activedescendant` and is widget-like in practice per WAI-ARIA APG's\n// toolbar pattern. jsx-a11y adds it for the same reason.\n//\n// `tooltip` is intentionally NOT added. Per WAI-ARIA 1.2 §5.3.3 — Document\n// Structure Roles (https://www.w3.org/TR/wai-aria-1.2/#tooltip), tooltip is\n// a document-structure role, not a widget; the spec says \"document structures\n// are not usually interactive.\" jsx-a11y agrees.\n//\n// `progressbar` IS included (it descends from widget → range per aria-query).\n// lit-a11y explicitly excludes it on the grounds that `progressbar.value` is\n// always readonly, so the role isn't operable by the user. This is a real\n// design disagreement; we side with aria-query's taxonomy (and jsx-a11y) to\n// keep the authority single-sourced. Authors wanting lit-a11y behavior can\n// layer a rule-level exclusion if needed.\nmodule.exports.INTERACTIVE_ROLES = buildInteractiveRoleSet();\n\n// Composite-widget child map — for each composite-widget parent role, the\n// set of child roles that are legitimately nested inside it per ARIA's\n// \"Required Owned Elements\" (aria-query's `requiredOwnedElements`). Closed\n// transitively over chains of composite widgets (e.g. `grid` owns `row`,\n// `row` owns `gridcell` / `columnheader` / `rowheader`, so `grid` transitively\n// allows all of them).\n//\n// This drives the nested-interactive exception so canonical composite-widget\n// patterns (`listbox > option`, `tablist > tab`, `tree > treeitem`,\n// `grid > row > gridcell`, `radiogroup > radio`, etc.) are not flagged.\nmodule.exports.COMPOSITE_WIDGET_CHILDREN = buildCompositeWidgetChildren();\n\nfunction buildInteractiveRoleSet() {\n  const result = new Set(['toolbar']);\n  for (const [role, def] of roles) {\n    if (def.abstract) {\n      continue;\n    }\n    const descendsFromWidget = (def.superClass || []).some((chain) => chain.includes('widget'));\n    if (descendsFromWidget) {\n      result.add(role);\n    }\n  }\n  return result;\n}\n\nfunction buildCompositeWidgetChildren() {\n  // Collect each role's own `requiredOwnedElements` first.\n  const own = new Map();\n  for (const [role, def] of roles) {\n    if (def.abstract) {\n      continue;\n    }\n    const owned = def.requiredOwnedElements;\n    if (!owned || owned.length === 0) {\n      continue;\n    }\n    const kids = new Set();\n    for (const chain of owned) {\n      for (const child of chain) {\n        kids.add(child);\n      }\n    }\n    own.set(role, kids);\n  }\n\n  // A role also legitimately owns whatever its ancestor roles in `superClass`\n  // own. This lets e.g. `treegrid` (superClass chains include `grid` and\n  // `tree`) inherit `row` from `grid` and `treeitem` from `tree`, matching\n  // the APG treegrid pattern.\n  const direct = new Map();\n  for (const [role, def] of roles) {\n    if (def.abstract) {\n      continue;\n    }\n    const merged = new Set(own.get(role) || []);\n    for (const chain of def.superClass || []) {\n      for (const ancestor of chain) {\n        const inherited = own.get(ancestor);\n        if (inherited) {\n          for (const child of inherited) {\n            merged.add(child);\n          }\n        }\n      }\n    }\n    if (merged.size > 0) {\n      direct.set(role, merged);\n    }\n  }\n\n  // Transitive closure: parent -> all roles reachable through owned-element chains.\n  const closed = new Map();\n  function expand(role, visited) {\n    if (closed.has(role)) {\n      return closed.get(role);\n    }\n    const out = new Set();\n    const kids = direct.get(role);\n    if (!kids) {\n      closed.set(role, out);\n      return out;\n    }\n    for (const child of kids) {\n      out.add(child);\n      if (!visited.has(child)) {\n        // Pass a fresh branch-specific visited set so sibling branches do not\n        // contaminate each other's traversal. A shared Set across branches\n        // makes memoized results order-dependent, because whether `child` is\n        // recursed into depends on which sibling ran first.\n        for (const grandchild of expand(child, new Set([...visited, child]))) {\n          out.add(grandchild);\n        }\n      }\n    }\n    closed.set(role, out);\n    return out;\n  }\n  for (const role of direct.keys()) {\n    expand(role, new Set([role]));\n  }\n  return closed;\n}\n"
  },
  {
    "path": "lib/utils/is-native-element.js",
    "content": "'use strict';\n\nconst htmlTags = require('html-tags');\nconst svgTags = require('svg-tags');\nconst { mathmlTagNames } = require('mathml-tag-names');\n\n// Authoritative set of native element tag names. Mirrors the approach\n// established by #2689 (template-no-block-params-for-html-elements), which\n// the maintainer requires for component-vs-element discrimination in this\n// plugin. Heuristic approaches (PascalCase detection, etc.) were explicitly\n// rejected there because a lowercase tag CAN be a component in GJS/GTS when\n// the name is bound in scope (e.g. `const div = MyComponent; <div />`).\nconst ELEMENT_TAGS = new Set([...htmlTags, ...svgTags, ...mathmlTagNames]);\n\n/**\n * Returns true if the Glimmer element node is a native HTML / SVG / MathML\n * element — i.e. the tag name is in the authoritative list AND is not\n * shadowed by an in-scope binding.\n *\n * \"Native\" here means **spec-registered tag name** (in the HTML, SVG, or\n * MathML spec registries, reached via the `html-tags` / `svg-tags` /\n * `mathml-tag-names` packages). It is NOT the same as:\n *\n *  - \"native accessibility\" / \"widget-ness\" — an ARIA-tree-semantics\n *    question (for example, whether something maps to a widget role)\n *  - \"native interactive content\" / \"focus behavior\" — an HTML content-model\n *    question about which elements are considered interactive in the spec\n *  - \"natively focusable\" / sequential-focus — see HTML §6.6.3\n *\n * This util answers only: \"is this tag a first-class built-in element of one\n * of the three markup-language standards, rather than a component invocation\n * or a shadowed local binding?\" Callers should combine it with whatever\n * accessibility, interactivity, or focusability checks they need for more\n * specific questions.\n *\n * Returns false for:\n *  - components (PascalCase, dotted, @-prefixed, this.-prefixed, ::-namespaced —\n *    none of these tag names appear in the HTML/SVG/MathML lists)\n *  - custom elements (`<my-widget>`) — accepted false negative; the web-\n *    components namespace is open and can't be enumerated\n *  - scope-bound identifiers (`<div>` when `div` is a local `let` / `const` /\n *    import / block-param in the enclosing scope)\n *\n * @param {object} node - GlimmerElementNode\n * @param {object} [sourceCode] - ESLint SourceCode, for scope lookup. When\n *   omitted, the scope check is skipped (the result is then list-based only —\n *   suitable for unit tests).\n */\nfunction isNativeElement(node, sourceCode) {\n  if (!node || typeof node.tag !== 'string') {\n    return false;\n  }\n  if (!ELEMENT_TAGS.has(node.tag)) {\n    return false;\n  }\n  if (!sourceCode || !node.parent) {\n    return true;\n  }\n  const scope = sourceCode.getScope(node.parent);\n  const firstPart = node.parts && node.parts[0];\n  // Walk the full scope chain so a module-level `const div = MyComponent`\n  // also shadows the native `<div>` tag, not just block-level bindings.\n  if (firstPart && hasBindingInScopeChain(scope, firstPart.name)) {\n    return false;\n  }\n  return true;\n}\n\nfunction hasBindingInScopeChain(scope, name) {\n  let current = scope;\n  while (current) {\n    if (current.variables && current.variables.some((v) => v.name === name)) {\n      return true;\n    }\n    current = current.upper;\n  }\n  return false;\n}\n\n/**\n * Inverse of {@link isNativeElement}. Returns true when the node should NOT\n * be treated as a native HTML element — either because it's a component\n * invocation (PascalCase, dotted, @-prefixed, this.-prefixed, custom element)\n * OR a tag name that's shadowed by a scope binding.\n */\nfunction isComponentInvocation(node, sourceCode) {\n  return !isNativeElement(node, sourceCode);\n}\n\nmodule.exports = { isNativeElement, isComponentInvocation, ELEMENT_TAGS };\n"
  },
  {
    "path": "lib/utils/javascript.js",
    "content": "'use strict';\n\nmodule.exports = {\n  duplicateArrays,\n  removeWhitespace,\n};\n\n/**\n * duplicateArrays([[\"a\", \"b\"]], 2) -> [[\"a\", \"b\"], [\"a\", \"b\"], [\"a\", \"b\"]]\n * @param {Array<Array>} arr\n * @param {number} times\n * @returns {Array<Array>}\n */\nfunction duplicateArrays(arr, times) {\n  const result = [];\n  for (let i = 0; i <= times; i++) {\n    result.push(...arr.map((a) => a.slice(0))); // eslint-disable-line unicorn/prefer-spread\n  }\n  return result;\n}\n\nfunction removeWhitespace(str) {\n  // Removes whitespace anywhere inside string.\n  return str.replaceAll(/\\s/g, '');\n}\n"
  },
  {
    "path": "lib/utils/jquery-methods.js",
    "content": "// This file is built by scripts/list-jquery-methods.js; do not edit it directly.\nmodule.exports = [\n  'Animation',\n  'Callbacks',\n  'Deferred',\n  'Event',\n  'Tween',\n  'ajax',\n  'ajaxPrefilter',\n  'ajaxSetup',\n  'ajaxTransport',\n  'attr',\n  'camelCase',\n  'cleanData',\n  'clone',\n  'contains',\n  'css',\n  'data',\n  'dequeue',\n  'each',\n  'error',\n  'escapeSelector',\n  'extend',\n  'filter',\n  'find',\n  'fx',\n  'get',\n  'getJSON',\n  'getScript',\n  'globalEval',\n  'grep',\n  'hasData',\n  'holdReady',\n  'htmlPrefilter',\n  'inArray',\n  'isArray',\n  'isEmptyObject',\n  'isFunction',\n  'isNumeric',\n  'isPlainObject',\n  'isWindow',\n  'isXMLDoc',\n  'makeArray',\n  'map',\n  'merge',\n  'noConflict',\n  'nodeName',\n  'noop',\n  'now',\n  'param',\n  'parseHTML',\n  'parseJSON',\n  'parseXML',\n  'post',\n  'prop',\n  'proxy',\n  'queue',\n  'ready',\n  'readyException',\n  'removeAttr',\n  'removeData',\n  'removeEvent',\n  'speed',\n  'style',\n  'text',\n  'trim',\n  'type',\n  'unique',\n  'uniqueSort',\n  'when',\n];\n"
  },
  {
    "path": "lib/utils/jquery.js",
    "content": "const { ReferenceTracker } = require('eslint-utils');\nconst jqueryMethods = require('../utils/jquery-methods');\n\nconst jqueryMap = {\n  [ReferenceTracker.CALL]: true,\n};\n\nfor (const method of jqueryMethods) {\n  jqueryMap[method] = { [ReferenceTracker.CALL]: true };\n}\n\n/**\n * Global references\n *\n * eg; $(body) and $.post()\n *\n * For use with ReferenceTracker: tracker.iterateGlobalReferences();\n */\nconst globalMap = {\n  $: jqueryMap,\n  jQuery: jqueryMap,\n};\n\n/**\n * ESM references\n *   import $ from 'jquery'\n *   import { $ as jq } from 'ember'\n *\n * eg;\n *   $(body) and jq.post()\n *\n * For use with ReferenceTracker: tracker.iterateEsmReferences();\n */\nconst esmMap = {\n  jquery: {\n    [ReferenceTracker.ESM]: true,\n    default: jqueryMap,\n  },\n  ember: {\n    [ReferenceTracker.ESM]: true,\n    default: {\n      $: jqueryMap,\n    },\n    $: jqueryMap,\n  },\n};\n\nmodule.exports = {\n  globalMap,\n  esmMap,\n};\n"
  },
  {
    "path": "lib/utils/landmark-roles.js",
    "content": "'use strict';\n\nconst { roles } = require('aria-query');\n\n// Non-abstract landmark roles derived from aria-query's role taxonomy: any\n// role whose superClass chain includes 'landmark', minus DPub-ARIA `doc-*`\n// (which share that superClass but are outside this plugin's rules' scope —\n// downstream callers can extend if needed). The exact size is intentionally\n// not hard-coded: the derivation is what matters, so if aria-query adds a\n// new non-abstract landmark upstream it will be picked up automatically.\nconst ALL_LANDMARK_ROLES = buildLandmarkRoleSet();\n\n// The subset that's safe for static-linting rules to treat as landmarks\n// without further verification. `region` is EXCLUDED because a static linter\n// cannot determine at lint time whether the element has an accessible name\n// (via aria-label / aria-labelledby / title), which is required for the\n// `region` role to actually apply to a `<section>` per HTML-AAM.\n//\n// See PR #2694 for the full rationale and spec citation\n// (https://www.w3.org/TR/html-aam-1.0/#el-section):\n// `<section>` without an accessible name has role `generic`, not `region`.\n//\n// Most a11y rules that enumerate landmarks should use this subset.\n// Rules that DO verify accessible names (e.g. template-no-duplicate-\n// landmark-elements, which inspects aria-label / aria-labelledby before\n// classifying a node as a landmark) should import ALL_LANDMARK_ROLES.\nconst LANDMARK_ROLES = new Set([...ALL_LANDMARK_ROLES].filter((role) => role !== 'region'));\n\nfunction buildLandmarkRoleSet() {\n  const result = new Set();\n  for (const [role, def] of roles) {\n    if (def.abstract) {\n      continue;\n    }\n    if (role.startsWith('doc-')) {\n      continue;\n    }\n    const descendsFromLandmark = (def.superClass || []).some((chain) => chain.includes('landmark'));\n    if (descendsFromLandmark) {\n      result.add(role);\n    }\n  }\n  return result;\n}\n\nmodule.exports = { LANDMARK_ROLES, ALL_LANDMARK_ROLES };\n"
  },
  {
    "path": "lib/utils/new-module.js",
    "content": "'use strict';\n\nconst { isIdentifier, isObjectPattern, isVariableDeclarator } = require('./types');\n\nmodule.exports = {\n  buildFix,\n  buildMessage,\n  getFullNames,\n  isDestructuring,\n};\n\nconst EMBER_NAMESPACES = new Set(['inject.controller', 'inject.service']);\n\n// Both the Controller module and Service module each make available the\n// \"inject\" namespace. In order to make it more readable, so it's more\n// explicit at first glance from which module the \"inject\" namespace\n// belongs to or is being imported from, let's check against this so we\n// can properly populate the import specifier to report the ESLint error.\n// Ex: import { inject as service } from '@ember/service';\nfunction isNamespace(obj) {\n  return EMBER_NAMESPACES.has(`${obj.parent}.${obj.key}`);\n}\n\nfunction isNamedExport(obj) {\n  return obj.match.export !== 'default';\n}\n\nfunction getReplacementSpecifier(obj) {\n  let importSpecifier;\n\n  if (isNamedExport(obj) && !isNamespace(obj)) {\n    importSpecifier =\n      obj.match.localName && obj.match.localName !== obj.match.export\n        ? `{ ${obj.match.export} as ${obj.match.localName} }`\n        : `{ ${obj.match.export} }`;\n  } else if (isNamedExport(obj) && isNamespace(obj)) {\n    importSpecifier = `{ ${obj.parent} as ${obj.key} }`;\n  } else {\n    importSpecifier = obj.match.localName || obj.customKey || obj.key;\n  }\n\n  return importSpecifier;\n}\n\nfunction buildMessage(obj) {\n  let message;\n\n  const importSpecifier = getReplacementSpecifier(obj);\n\n  const replacement = `import ${importSpecifier} from '${obj.match.module}';`;\n\n  if (obj.type === 'Property') {\n    const global = obj.match.global.startsWith('DS') ? 'DS' : 'Ember';\n    message = `Use \\`${replacement}\\` instead of using ${global} destructuring`;\n  } else {\n    message = `Use \\`${replacement}\\` instead of using ${obj.fullName}`;\n  }\n\n  return message;\n}\n\n/**\n * This function returns a fix function. The fix function can update the old\n * imports to the new ones.\n * @param {Object} node - the AST node related to the import problem\n * @param {Object} modulesData - the data set that maps old imports to the new\n * ones. It is an object like this one:\n * {\n *   'ember-array': {\n *     default: ['@ember/array'],\n *   },\n *   'ember-array/mutable': {\n *     default: ['@ember/array/mutable'],\n *   },\n *   'ember-array/utils': {\n *     A: ['@ember/array', 'A'],\n *     isEmberArray: ['@ember/array', 'isArray'],\n *     wrap: ['@ember/array', 'makeArray'],\n *   },\n * }\n * @return {function(fixer: Object): function} a function that applies a fix to\n * update the import\n */\nfunction buildFix(node, modulesData) {\n  const moduleName = node.source.value;\n\n  function fix(fixer) {\n    const newImports = buildNewImports(node, modulesData, moduleName);\n\n    const lines = buildLines(newImports);\n\n    return fixer.replaceText(node, lines.join('\\n'));\n  }\n\n  return fix;\n}\n\nfunction buildNewImports(node, modulesData, moduleName) {\n  const newImports = {};\n\n  for (const specifier of node.specifiers) {\n    const localName = specifier.local.name;\n\n    let importedName =\n      specifier.type === 'ImportDefaultSpecifier' ? 'default' : specifier.imported.name;\n\n    let module;\n    const moduleMapping = modulesData[moduleName][importedName];\n    if (moduleMapping) {\n      module = moduleMapping[0];\n      importedName = moduleMapping[1] || 'default';\n    } else {\n      module = moduleName;\n    }\n\n    newImports[module] = newImports[module] || [];\n    newImports[module].push({ localName, importedName });\n  }\n\n  return newImports;\n}\n\nfunction buildLines(newImports) {\n  return Object.keys(newImports).map((module) => {\n    const newModuleImport = newImports[module];\n\n    const defaultImport = newModuleImport\n      .filter((it) => it.importedName === 'default')\n      .map((it) => it.localName);\n\n    const namedImports = newModuleImport\n      .filter((it) => it.importedName !== 'default')\n      .map((it) =>\n        it.importedName === it.localName ? it.importedName : `${it.importedName} as ${it.localName}`\n      )\n      .join(', ');\n\n    const specifiers = [...defaultImport, namedImports ? `{ ${namedImports} }` : '']\n      .filter(Boolean)\n      .join(', ');\n\n    return `import ${specifiers} from '${module}';`;\n  });\n}\n\nfunction getFullNames(prefix, node) {\n  let fullName = prefix;\n  const fullNames = [];\n\n  let parentNode = node.parent;\n  while (parentNode.type === 'MemberExpression') {\n    fullName += `.${parentNode.property.name}`;\n    fullNames.unshift(fullName);\n    parentNode = parentNode.parent;\n  }\n\n  return fullNames;\n}\n\nfunction isDestructuring(node) {\n  return (\n    isVariableDeclarator(node) &&\n    node.init &&\n    isIdentifier(node.init) &&\n    node.id &&\n    isObjectPattern(node.id)\n  );\n}\n"
  },
  {
    "path": "lib/utils/property-getter.js",
    "content": "const types = require('./types');\nconst javascriptUtils = require('./javascript');\n\nmodule.exports = {\n  nodeToDependentKey,\n  isSimpleThisExpression,\n  isThisGetCall,\n};\n\n/**\n * Checks if a CallExpression node looks like `this.get('property')`.\n *\n * @param {Node} node The CallExpression node to check.\n * @returns {boolean} Whether the node looks like `this.get('property')`.\n */\nfunction isThisGetCall(node) {\n  if (\n    types.isCallExpression(node) &&\n    types.isMemberExpression(node.callee) &&\n    types.isThisExpression(node.callee.object) &&\n    types.isIdentifier(node.callee.property) &&\n    node.callee.property.name === 'get' &&\n    node.arguments.length === 1 &&\n    types.isStringLiteral(node.arguments[0])\n  ) {\n    // Looks like: this.get('property')\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Checks if a MemberExpression node looks like:\n * * `this.x`\n * * `this.x.y`\n * * `this.x?.y`\n * * `this.get('x')`.\n *\n * @param {Node} node The MemberExpression node to check.\n * @returns {boolean} Whether the node looks as expected.\n */\nfunction isSimpleThisExpression(node) {\n  if (isThisGetCall(node)) {\n    return true;\n  }\n\n  if (node.type === 'ChainExpression') {\n    return isSimpleThisExpression(node.expression);\n  }\n\n  if (!(types.isMemberExpression(node) || types.isOptionalMemberExpression(node))) {\n    return false;\n  }\n\n  let current = node;\n  while (current !== null) {\n    if (\n      (types.isMemberExpression(current) || types.isOptionalMemberExpression(current)) &&\n      !current.computed\n    ) {\n      if (!types.isIdentifier(current.property)) {\n        return false;\n      }\n      current = current.object;\n    } else if (types.isThisExpression(current)) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  return false;\n}\n/**\n * Converts a Node containing a ThisExpression to its dependent key.\n *\n * Example Input:   A Node with this source code: `this.x.y` or `this.get('x')`\n * Example Output:  'x.y'\n *\n * @param {Node} node a MemberExpression node that looks like `this.x.y` or `this.get('x')`.\n * @returns {String | undefined} The dependent key of the input node (without `this.`).\n */\nfunction nodeToDependentKey(nodeWithThisExpression, context) {\n  if (types.isCallExpression(nodeWithThisExpression)) {\n    if (isThisGetCall(nodeWithThisExpression)) {\n      // Looks like: this.get('property')\n      return nodeWithThisExpression.arguments[0].value;\n    }\n\n    // Looks like: this.someMethod() or this.foo.bar(1)\n    return undefined;\n  }\n\n  const sourceCode = context.sourceCode;\n  return javascriptUtils.removeWhitespace(\n    sourceCode\n      .getText(nodeWithThisExpression)\n      .replace(/^this\\??\\./, '')\n      .replaceAll('?.', '.') // Replace any optional chaining.\n  );\n}\n"
  },
  {
    "path": "lib/utils/property-order.js",
    "content": "'use strict';\n\nconst ember = require('./ember');\nconst utils = require('./utils');\nconst types = require('./types');\nconst decoratorUtils = require('../utils/decorators');\n\nmodule.exports = {\n  determinePropertyType,\n  reportUnorderedProperties,\n  addBackwardsPosition,\n};\n\nconst NAMES = {\n  actions: 'actions hash',\n  activate: 'lifecycle hook',\n  afterModel: 'lifecycle hook',\n  attribute: 'attribute',\n  beforeModel: 'lifecycle hook',\n  controller: 'controller injection',\n  deactivate: 'lifecycle hook',\n  didDestroyElement: 'lifecycle hook',\n  didInsertElement: 'lifecycle hook',\n  didReceiveAttrs: 'lifecycle hook',\n  didRender: 'lifecycle hook',\n  didUpdate: 'lifecycle hook',\n  didUpdateAttrs: 'lifecycle hook',\n  'empty-method': 'empty method',\n  init: 'lifecycle hook',\n  'lifecycle-hook': 'lifecycle hook',\n  method: 'method',\n  model: '\"model\" hook',\n  observer: 'observer',\n  property: 'property',\n  'single-line-function': 'single-line function',\n  'multi-line-function': 'multi-line function',\n  'query-params': 'property',\n  redirect: 'lifecycle hook',\n  relationship: 'relationship',\n  renderTemplate: 'lifecycle hook',\n  resetController: 'lifecycle hook',\n  serialize: 'lifecycle hook',\n  service: 'service injection',\n  setupController: 'lifecycle hook',\n  spread: 'spread property',\n  unknown: 'unknown property type',\n  willClearRender: 'lifecycle hook',\n  willDestroyElement: 'lifecycle hook',\n  willInsertElement: 'lifecycle hook',\n  willRender: 'lifecycle hook',\n  willUpdate: 'lifecycle hook',\n};\n\n// eslint-disable-next-line complexity\nfunction determinePropertyType(\n  node,\n  parentType,\n  ORDER,\n  importedEmberName,\n  importedInjectName,\n  importedObserverName,\n  importedControllerName\n) {\n  if (ember.isInjectedServiceProp(node, importedEmberName, importedInjectName)) {\n    return 'service';\n  }\n\n  if (ember.isInjectedControllerProp(node, importedEmberName, importedControllerName)) {\n    return 'controller';\n  }\n\n  if (types.isIdentifier(node.key) && node.key.name === 'init') {\n    return 'init';\n  }\n\n  if (parentType === 'component') {\n    if (types.isIdentifier(node.key) && ember.isComponentLifecycleHook(node)) {\n      return node.key.name;\n    }\n  }\n\n  if (parentType === 'controller') {\n    if (types.isIdentifier(node.key) && node.key.name === 'queryParams') {\n      return 'query-params';\n    } else if (ember.isControllerDefaultProp(node)) {\n      return 'inherited-property';\n    }\n  }\n\n  if (parentType === 'model') {\n    if (\n      (node.value && ember.isModule(node.value, 'attr', 'DS')) ||\n      decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(node, 'attr')\n    ) {\n      return 'attribute';\n    } else if (ember.isRelation(node)) {\n      return 'relationship';\n    }\n  }\n\n  if (parentType === 'route') {\n    if (ember.isRouteDefaultProp(node)) {\n      return 'inherited-property';\n    } else if (ember.isRouteLifecycleHook(node)) {\n      return node.key.name;\n    }\n  }\n\n  if (ember.isObserverProp(node, importedEmberName, importedObserverName)) {\n    return 'observer';\n  }\n\n  if (parentType !== 'model' && ember.isActionsProp(node)) {\n    return 'actions';\n  }\n\n  if (ember.isSingleLineFn(node, importedEmberName, importedObserverName)) {\n    return 'single-line-function';\n  }\n\n  if (ember.isMultiLineFn(node, importedEmberName, importedObserverName)) {\n    return 'multi-line-function';\n  }\n\n  const propName = getNodeKeyName(node);\n  const possibleOrderName = `custom:${propName}`;\n  if (ORDER.includes(possibleOrderName)) {\n    return possibleOrderName;\n  }\n\n  if (ember.isCustomProp(node)) {\n    return 'property';\n  }\n\n  if (node.value && ember.isFunctionExpression(node.value)) {\n    if (utils.isEmptyMethod(node)) {\n      return 'empty-method';\n    }\n\n    return 'method';\n  }\n\n  // Handle both babel-eslint and @babel/eslint-parser\n  if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') {\n    return 'spread';\n  }\n\n  return 'unknown';\n}\n\nfunction getOrder(ORDER, type) {\n  for (let i = 0, len = ORDER.length; i < len; i++) {\n    const value = ORDER[i];\n    if (typeof value === 'string') {\n      if (value === type) {\n        return i;\n      }\n    } else {\n      const index = value.indexOf(type);\n      if (index !== -1) {\n        return i;\n      }\n    }\n  }\n\n  return ORDER.length;\n}\n\nfunction getNodeKeyName(node) {\n  if (node.key) {\n    if (node.key.type === 'Identifier') {\n      return node.key.name;\n    } else if (node.key.type === 'Literal' && typeof node.key.value === 'string') {\n      return node.key.value;\n    }\n  }\n\n  return 'unknown';\n}\n\nfunction getName(type, node) {\n  let prefix;\n  if (!node.computed && type !== 'actions' && type !== 'model' && type !== 'spread') {\n    prefix = getNodeKeyName(node);\n  }\n\n  const typeDescription = NAMES[type];\n\n  if (type === 'inherited-property') {\n    return prefix ? `inherited \"${prefix}\" property` : 'inherited property';\n  }\n\n  if (type.startsWith('custom:')) {\n    return prefix ? `\"${prefix}\" custom property` : 'custom property';\n  }\n\n  return prefix ? `\"${prefix}\" ${typeDescription}` : typeDescription;\n}\n\nfunction reportUnorderedProperties(\n  node,\n  context,\n  parentType,\n  ORDER,\n  importedEmberName,\n  importedInjectName,\n  importedObserverName,\n  importedControllerName,\n  scopeManager\n) {\n  const isNativeClass = types.isClassDeclaration(node) || node.type === 'ClassExpression';\n  let maxOrder = -1;\n  const firstPropertyOfType = {};\n  let firstPropertyOfNextType;\n\n  const properties =\n    types.isClassDeclaration(node) || node.type === 'ClassExpression'\n      ? node.body.body\n      : ember.getModuleProperties(node, scopeManager);\n\n  for (const property of properties) {\n    const type = determinePropertyType(\n      property,\n      parentType,\n      ORDER,\n      importedEmberName,\n      importedInjectName,\n      importedObserverName,\n      importedControllerName\n    );\n    const order = getOrder(ORDER, type);\n\n    const info = {\n      node: property,\n      type,\n    };\n\n    // check if this property should be moved further upwards\n    if (order < maxOrder) {\n      // look for correct position to insert this property\n      for (let i = order + 1; i <= maxOrder; i++) {\n        firstPropertyOfNextType = firstPropertyOfType[i];\n        if (firstPropertyOfNextType) {\n          break;\n        }\n      }\n\n      const typeName = getName(info.type, info.node);\n      const nextTypeName = getName(firstPropertyOfNextType.type, firstPropertyOfNextType.node);\n      const nextSourceLine = firstPropertyOfNextType.node.loc.start.line;\n\n      context.report({\n        node: property,\n        message: `The ${typeName} should be above the ${nextTypeName} on line ${nextSourceLine}`,\n        fix: (fixer) => {\n          // for capturing the moved property and EOL character to insert ',' in between\n          const propertyWithEOL = /(.+)(\\s+)$/;\n          const sourceCode = context.sourceCode;\n          const foundProperty = firstPropertyOfNextType.node;\n          let nextToken = property;\n          let optionalComma = '';\n          let isLastProperty = false;\n\n          // including EOL character(s)\n          do {\n            const previousToken = nextToken;\n            nextToken = sourceCode.getTokenAfter(nextToken, {\n              includeComments: true,\n            });\n\n            if (!nextToken) {\n              nextToken = previousToken;\n            }\n\n            // adding a trailing comma when it's the last property defined in object literals\n            if (nextToken.value === '}') {\n              isLastProperty = true;\n\n              if (!isNativeClass && previousToken.value !== ',') {\n                optionalComma = ',';\n              }\n            }\n          } while (nextToken.value === ',');\n\n          // additional whitespace is needed only when it's the last property in object literals\n          const whitespaceCount = !isNativeClass && isLastProperty ? property.loc.start.column : 0;\n\n          const propertyOffset = getCommentOffsetBefore(property, sourceCode);\n          const foundPropertyOffset = getCommentOffsetBefore(foundProperty, sourceCode);\n\n          const offset = nextToken.range[0] - property.range[1];\n          const textBetween =\n            property.range[0] - propertyOffset - foundProperty.range[1] - whitespaceCount;\n\n          const replaceTextRange = [\n            foundProperty.range[0] - foundPropertyOffset,\n            nextToken.range[0],\n          ];\n          const movedProperty = sourceCode.getText(property, propertyOffset, offset);\n          const restText = sourceCode.getText(foundProperty, foundPropertyOffset, textBetween);\n\n          // adding the optional comma between the property and newline\n          const replaceWithComma = `$1${optionalComma}$2`;\n          const movedPropertyWithComma = movedProperty.replace(propertyWithEOL, replaceWithComma);\n          const optionalWhitespace = Array.from({ length: whitespaceCount + 1 }).join(' ');\n\n          const outputText = movedPropertyWithComma + optionalWhitespace + restText;\n\n          return fixer.replaceTextRange(replaceTextRange, outputText);\n        },\n      });\n    } else {\n      maxOrder = order;\n\n      if (!(order in firstPropertyOfType)) {\n        firstPropertyOfType[order] = info;\n      }\n    }\n  }\n}\n\nfunction getCommentOffsetBefore(property, sourceCode) {\n  const commentBlockRegExp = /^\\/\\*(.|\\s)*\\*\\/$/m;\n  const commentLineRegExp = /^\\/\\/.*$/;\n  const previousToken = sourceCode.getTokenBefore(property, {\n    includeComments: true,\n  });\n  const previousTokenText = sourceCode.getText(previousToken);\n\n  // including comments above the moved property\n  const isLineComment = previousToken.type === 'Line' && commentLineRegExp.test(previousTokenText);\n  const isBlockComment =\n    previousToken.type === 'Block' && commentBlockRegExp.test(previousTokenText);\n\n  if (isLineComment || isBlockComment) {\n    return property.range[0] - previousToken.range[0];\n  }\n\n  return 0;\n}\n\nfunction addBackwardsPosition(order, newPosition, targetPosition) {\n  const positionOrder = [...order];\n\n  const containsPosition = positionOrder.some((position) => {\n    if (Array.isArray(position)) {\n      return position.includes(newPosition);\n    }\n\n    return position === newPosition;\n  });\n\n  if (!containsPosition) {\n    const targetIndex = positionOrder.indexOf(targetPosition);\n    if (targetIndex > -1) {\n      positionOrder[targetIndex] = [targetPosition, newPosition];\n    } else {\n      for (const position of positionOrder) {\n        if (Array.isArray(position) && position.includes(targetPosition)) {\n          position.push(newPosition);\n        }\n      }\n    }\n  }\n\n  return positionOrder;\n}\n"
  },
  {
    "path": "lib/utils/property-setter.js",
    "content": "const types = require('./types');\n\nmodule.exports = {\n  isThisSet,\n};\n\n/**\n * Checks if an AssignmentExpression looks like `this.x =` or `this.x.y =`.\n *\n * @param {Node} node The AssignmentExpression node to check.\n * @returns {boolean} Whether the node looks as expected.\n */\nfunction isThisSet(node) {\n  if (!types.isAssignmentExpression(node)) {\n    return false;\n  }\n\n  let current = node.left;\n  if (!types.isMemberExpression(current)) {\n    return false;\n  }\n  while (types.isMemberExpression(current.object)) {\n    current = current.object;\n  }\n  if (!types.isThisExpression(current.object)) {\n    return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "lib/utils/scope-references-this.js",
    "content": "'use strict';\n\nconst estraverse = require('estraverse');\n\n/**\n * Determines whether this AST node uses the `this` of the surrounding context.\n *\n * @param {ASTNode} node\n * @returns {boolean}\n */\nfunction scopeReferencesThis(node) {\n  let result = false;\n\n  estraverse.traverse(node, {\n    enter(node) {\n      switch (node.type) {\n        case 'FunctionDeclaration':\n        case 'FunctionExpression': {\n          this.skip();\n          break;\n        }\n\n        case 'ThisExpression': {\n          result = true;\n          this.break();\n          break;\n        }\n\n        default: {\n          // Ignored.\n          break;\n        }\n      }\n    },\n    fallback: 'iteration',\n  });\n\n  return result;\n}\n\nmodule.exports = scopeReferencesThis;\n"
  },
  {
    "path": "lib/utils/stack.js",
    "content": "module.exports = class Stack {\n  constructor() {\n    this.stack = new Array();\n  }\n  pop() {\n    return this.stack.pop();\n  }\n  push(item) {\n    this.stack.push(item);\n  }\n  peek() {\n    return this.stack.length > 0 ? this.stack.at(-1) : undefined;\n  }\n  size() {\n    return this.stack.length;\n  }\n};\n"
  },
  {
    "path": "lib/utils/static-attr-value.js",
    "content": "'use strict';\n\n/**\n * Return the statically-known string value of a Glimmer attribute value node,\n * or `undefined` when the value is dynamic (cannot be resolved at lint time).\n *\n * Unwraps:\n *   - GlimmerTextNode → chars\n *   - GlimmerMustacheStatement with a literal path (boolean/string/number) → stringified value\n *   - GlimmerConcatStatement whose parts are all statically resolvable → joined string\n *\n * A missing/undefined value (valueless attribute, e.g. `<input disabled>`)\n * returns the empty string. Pass `attr.value` — not the attribute itself.\n */\nfunction getStaticAttrValue(value) {\n  if (value === null || value === undefined) {\n    return '';\n  }\n  if (value.type === 'GlimmerTextNode') {\n    return value.chars;\n  }\n  if (value.type === 'GlimmerMustacheStatement') {\n    return extractLiteral(value.path);\n  }\n  if (value.type === 'GlimmerConcatStatement') {\n    const parts = value.parts || [];\n    let out = '';\n    for (const part of parts) {\n      if (part.type === 'GlimmerTextNode') {\n        out += part.chars;\n        continue;\n      }\n      if (part.type === 'GlimmerMustacheStatement') {\n        const literal = extractLiteral(part.path);\n        if (literal === undefined) {\n          return undefined;\n        }\n        out += literal;\n        continue;\n      }\n      return undefined;\n    }\n    return out;\n  }\n  return undefined;\n}\n\nfunction extractLiteral(path) {\n  if (!path) {\n    return undefined;\n  }\n  if (path.type === 'GlimmerBooleanLiteral') {\n    return path.value ? 'true' : 'false';\n  }\n  if (path.type === 'GlimmerStringLiteral') {\n    return path.value;\n  }\n  if (path.type === 'GlimmerNumberLiteral') {\n    return String(path.value);\n  }\n  return undefined;\n}\n\nmodule.exports = { getStaticAttrValue };\n"
  },
  {
    "path": "lib/utils/types.js",
    "content": "'use strict';\n\n/**\n * Trivial helpers in this file that only check a node's existence and/or type are deprecated in favor of inlining that check.\n * We don't need a function for every type of node.\n * And as written, these functions won't correctly narrow the type of the node, which we would need if we incorporate TypeScript: https://github.com/ember-cli/eslint-plugin-ember/issues/1613\n * TODO: we should inline these trivial checks and only check for node existence when it's actually a possibility a node might not exist.\n */\n\nmodule.exports = {\n  isAnyFunctionExpression,\n  isArrayExpression,\n  isArrowFunctionExpression,\n  isAssignmentExpression,\n  isBinaryExpression,\n  isCallExpression,\n  isCallWithFunctionExpression,\n  isClassDeclaration,\n  isClassPropertyOrPropertyDefinition,\n  isCommaToken,\n  isConciseArrowFunctionWithCallExpression,\n  isConditionalExpression,\n  isDecorator,\n  isExpressionStatement,\n  isFunctionDeclaration,\n  isFunctionExpression,\n  isIdentifier,\n  isImportDeclaration,\n  isImportDefaultSpecifier,\n  isLiteral,\n  isLogicalExpression,\n  isMemberExpression,\n  isMethodDefinition,\n  isNewExpression,\n  isObjectExpression,\n  isObjectPattern,\n  isOptionalCallExpression,\n  isOptionalMemberExpression,\n  isProperty,\n  isReturnStatement,\n  isSpreadElement,\n  isString,\n  isStringLiteral,\n  isTaggedTemplateExpression,\n  isTemplateLiteral,\n  isThisExpression,\n  isUnaryExpression,\n  isVariableDeclarator,\n};\n\n/**\n * Check whether or not a node is an ArrowFunctionExpression or FunctionExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ArrowFunctionExpression or FunctionExpression.\n */\nfunction isAnyFunctionExpression(node) {\n  return isArrowFunctionExpression(node) || isFunctionExpression(node);\n}\n\n/**\n * Check whether or not a node is an ArrayExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ArrayExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isArrayExpression(node) {\n  return node !== undefined && node.type === 'ArrayExpression';\n}\n\n/**\n * Check whether or not a node is an ArrowFunctionExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ArrowFunctionExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isArrowFunctionExpression(node) {\n  return node !== undefined && node.type === 'ArrowFunctionExpression';\n}\n\n/**\n * Check whether or not a node is an AssignmentExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an AssignmentExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isAssignmentExpression(node) {\n  return node.type === 'AssignmentExpression';\n}\n\n/**\n * Check whether or not a node is an BinaryExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an BinaryExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isBinaryExpression(node) {\n  return node !== undefined && node.type === 'BinaryExpression';\n}\n\n/**\n * Check whether or not a node is an CallExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an CallExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isCallExpression(node) {\n  return node !== undefined && node.type === 'CallExpression';\n}\n\n/**\n * Check whether or not a node is a CallExpression that has a FunctionExpression\n * as first argument, eg.:\n * tSomeAction: mysteriousFnc(function(){})\n *\n * @param  {Object} node The node to check\n * @return {boolean} Whether or not the node is a call with a function expression as the first argument\n */\nfunction isCallWithFunctionExpression(node) {\n  if (!isCallExpression(node)) {\n    return false;\n  }\n  const callObj = isMemberExpression(node.callee) ? node.callee.object : node;\n  const firstArg = callObj.arguments ? callObj.arguments[0] : null;\n  return (\n    callObj !== undefined && isCallExpression(callObj) && firstArg && isFunctionExpression(firstArg)\n  );\n}\n\n/**\n * Check whether or not a node is a ClassDeclaration.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a ClassDeclaration.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isClassDeclaration(node) {\n  return node !== undefined && node.type === 'ClassDeclaration';\n}\n\n/**\n * Check whether or not a node is a ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8).\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a ClassProperty or PropertyDefinition.\n */\nfunction isClassPropertyOrPropertyDefinition(node) {\n  return (\n    node !== undefined && (node.type === 'ClassProperty' || node.type === 'PropertyDefinition')\n  );\n}\n\nfunction isCommaToken(token) {\n  return token.type === 'Punctuator' && token.value === ',';\n}\n\n/**\n * Check whether or not a node is an ArrowFunctionExpression with concise body\n * that contains a call expression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ArrowFunctionExpression\n * with concise body.\n */\nfunction isConciseArrowFunctionWithCallExpression(node) {\n  return isArrowFunctionExpression(node) && node.expression && isCallExpression(node.body);\n}\n\n/**\n * Check whether or not a node is a ConditionalExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a ConditionalExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isConditionalExpression(node) {\n  return node !== undefined && node.type === 'ConditionalExpression';\n}\n\n/**\n * Check whether or not a node is a Decorator.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a Decorator.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isDecorator(node) {\n  return node !== undefined && node.type === 'Decorator';\n}\n\n/**\n * Check whether or not a node is an ExpressionStatement.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ExpressionStatement.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isExpressionStatement(node) {\n  return node !== undefined && node.type === 'ExpressionStatement';\n}\n\n/**\n * Check whether or not a node is a FunctionDeclaration\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a FunctionDeclaration\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isFunctionDeclaration(node) {\n  return node !== undefined && node.type === 'FunctionDeclaration';\n}\n\n/**\n * Check whether or not a node is an FunctionExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an FunctionExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isFunctionExpression(node) {\n  return node !== undefined && node.type === 'FunctionExpression';\n}\n\n/**\n * Check whether or not a node is an Identifier.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an Identifier.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isIdentifier(node) {\n  return node !== undefined && node.type === 'Identifier';\n}\n\n/**\n * Check whether or not a node is an ImportDeclaration.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ImportDeclaration.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isImportDeclaration(node) {\n  return node !== undefined && node.type === 'ImportDeclaration';\n}\n\n/**\n * Check whether or not a node is an ImportDefaultSpecifier.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ImportDefaultSpecifier.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isImportDefaultSpecifier(node) {\n  return node !== undefined && node.type === 'ImportDefaultSpecifier';\n}\n\n/**\n * Check whether or not a node is an Literal.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an Literal.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isLiteral(node) {\n  return node !== undefined && node.type === 'Literal';\n}\n\n/**\n * Check whether or not a node is an LogicalExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an LogicalExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isLogicalExpression(node) {\n  return node !== undefined && node.type === 'LogicalExpression';\n}\n\n/**\n * Check whether or not a node is an MemberExpression.\n *\n * @param {Object} node The node to check.\n * @return {boolean} Whether or not the node is an MemberExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isMemberExpression(node) {\n  return node !== undefined && node.type === 'MemberExpression';\n}\n\n/**\n * Check whether or not a node is a MethodDefinition.\n *\n * @param {Object} node The node to check.\n * @return {boolean} Whether or not the node is a MethodDefinition.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isMethodDefinition(node) {\n  return node !== undefined && node.type === 'MethodDefinition';\n}\n\n/**\n * Check whether or not a node is an NewExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an NewExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isNewExpression(node) {\n  return node !== undefined && node.type === 'NewExpression';\n}\n\n/**\n * Check whether or not a node is an ObjectExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ObjectExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isObjectExpression(node) {\n  return node !== undefined && node.type === 'ObjectExpression';\n}\n\n/**\n * Check whether or not a node is an ObjectPattern.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ObjectPattern.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isObjectPattern(node) {\n  return node !== undefined && node.type === 'ObjectPattern';\n}\n\n/**\n * Check whether or not a node is an OptionalCallExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an OptionalCallExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isOptionalCallExpression(node) {\n  return node.type === 'OptionalCallExpression';\n}\n\n/**\n * Check whether or not a node is an OptionalMemberExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an OptionalMemberExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isOptionalMemberExpression(node) {\n  return node.type === 'OptionalMemberExpression';\n}\n\n/**\n * Check whether or not a node is an Property.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an Property.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isProperty(node) {\n  return node !== undefined && node.type === 'Property';\n}\n\n/**\n * Check whether or not a node is a ReturnStatement\n *\n * @param {Object} node The node to check.\n * @return {Boolean} Whether or not the node is a ReturnStatement.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isReturnStatement(node) {\n  return node !== undefined && node.type && node.type === 'ReturnStatement';\n}\n\n/**\n * Check whether or not a node is a SpreadElement\n *\n * @param {Object} node The node to check.\n * @return {Boolean} Whether or not the node is a SpreadElement.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isSpreadElement(node) {\n  return node !== undefined && node.type === 'SpreadElement';\n}\n\nfunction isString(node) {\n  return isTemplateLiteral(node) || (isLiteral(node) && typeof node.value === 'string');\n}\n\nfunction isStringLiteral(node) {\n  return isLiteral(node) && typeof node.value === 'string';\n}\n\n/**\n * Check whether or not a node is a TaggedTemplateExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a TaggedTemplateExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isTaggedTemplateExpression(node) {\n  return node !== undefined && node.type === 'TaggedTemplateExpression';\n}\n\n/**\n * Check whether or not a node is a TemplateLiteral.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a TemplateLiteral.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isTemplateLiteral(node) {\n  return node !== undefined && node.type === 'TemplateLiteral';\n}\n\n/**\n * Check whether or not a node is an ThisExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an ThisExpression.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isThisExpression(node) {\n  return node !== undefined && node.type === 'ThisExpression';\n}\n\n/**\n * Check whether or not a node is an UnaryExpression.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an Literal.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isUnaryExpression(node) {\n  return node !== undefined && node.type === 'UnaryExpression';\n}\n\n/**\n * Check whether or not a node is a VariableDeclarator.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is a VariableDeclarator.\n * @deprecated trivial helpers are deprecated in favor of inlining the type check\n */\nfunction isVariableDeclarator(node) {\n  return node !== undefined && node.type === 'VariableDeclarator';\n}\n"
  },
  {
    "path": "lib/utils/utils.js",
    "content": "'use strict';\n\nconst {\n  isAssignmentExpression,\n  isCallExpression,\n  isIdentifier,\n  isLiteral,\n  isMemberExpression,\n  isNewExpression,\n  isObjectPattern,\n  isOptionalCallExpression,\n  isOptionalMemberExpression,\n  isThisExpression,\n} = require('../utils/types');\nconst { findVariable } = require('eslint-utils');\n\nmodule.exports = {\n  collectObjectPatternBindings,\n  findNodes,\n  findUnorderedProperty,\n  getAncestor,\n  getName,\n  getNodeOrNodeFromVariable,\n  getPropertyValue,\n  getSize,\n  isEmptyMethod,\n  isInLeftSideOfAssignmentExpression,\n  parseArgs,\n  parseCallee,\n  startsWithThisExpression,\n};\n\n/**\n * Find nodes of given name\n *\n * @param  {Node[]} body Array of nodes\n * @param  {String} nodeName\n * @return {Node[]}\n */\nfunction findNodes(body, nodeName) {\n  let nodesArray = [];\n\n  if (body) {\n    nodesArray = body.filter((node) => node.type === nodeName);\n  }\n\n  return nodesArray;\n}\n\n/**\n * Get size of expression in lines\n *\n * @param  {Object} node The node to check.\n * @return {Integer} Number of lines\n */\nfunction getSize(node) {\n  return node.loc.end.line - node.loc.start.line + 1;\n}\n\n/**\n * Parse CallExpression or NewExpression to get array of properties and object name\n *\n * @param  {Object} node The node to parse\n * @return {String[]} eg. ['Ember', 'computed', 'alias']\n */\nfunction parseCallee(node) {\n  const parsedCallee = [];\n  let callee;\n\n  if (isCallExpression(node) || isNewExpression(node)) {\n    callee = node.callee;\n\n    while (isMemberExpression(callee)) {\n      if (isIdentifier(callee.property)) {\n        parsedCallee.push(callee.property.name);\n      }\n      callee = callee.object;\n    }\n\n    if (isIdentifier(callee)) {\n      parsedCallee.push(callee.name);\n    }\n  }\n\n  return parsedCallee.reverse();\n}\n\n/**\n * Parse CallExpression to get array of arguments\n *\n * @param  {Object} node Node to parse\n * @return {String[]} Literal function's arguments\n */\nfunction parseArgs(node) {\n  let parsedArgs = [];\n\n  if (isCallExpression(node)) {\n    parsedArgs = node.arguments\n      .filter((argument) => isLiteral(argument) && argument.value)\n      .map((argument) => argument.value);\n  }\n\n  return parsedArgs;\n}\n\n/**\n * Find property that is in wrong order\n *\n * @param  {Object[]} arr Properties with their order value\n * @return {Object}       Unordered property or null\n */\nfunction findUnorderedProperty(arr) {\n  for (let i = 0; i < arr.length - 1; i++) {\n    if (arr[i].order > arr[i + 1].order) {\n      return arr[i];\n    }\n  }\n\n  return null;\n}\n\n/**\n * Gets a property's value either by property path.\n *\n * @example\n * getPropertyValue('name');\n * getPropertyValue('parent.key.name');\n *\n * @param {Object} node\n * @param {String} path\n * @returns\n */\nfunction getPropertyValue(node, path) {\n  const parts = typeof path === 'string' ? path.split('.') : path;\n\n  if (parts.length === 1) {\n    return node[path];\n  }\n\n  const property = node[parts[0]];\n\n  if (property && parts.length > 1) {\n    parts.shift();\n    return getPropertyValue(property, parts);\n  }\n\n  return property;\n}\n\n/**\n * Find deconstructed bindings based on the initialObjToBinding hash.\n *\n * Extracts the names of destructured properties, even if they are aliased.\n * `initialObjToBinding` should should have variable names as keys and bindings array as values.\n * Given `const { $: foo } = Ember` it will return `['foo']`.\n *\n * @param  {VariableDeclarator} node node to parse\n * @param  {Object}             initialObjToBinding relevant bindings\n * @return {String[]}           list of object pattern bindings\n */\nfunction collectObjectPatternBindings(node, initialObjToBinding) {\n  if (!isObjectPattern(node.id)) {\n    return [];\n  }\n\n  const identifiers = Object.keys(initialObjToBinding);\n  const objBindingName = node.init.name;\n  const bindingIndex = identifiers.indexOf(objBindingName);\n  if (bindingIndex === -1) {\n    return [];\n  }\n\n  const binding = identifiers[bindingIndex];\n\n  return node.id.properties\n    .filter((props) => initialObjToBinding[binding].includes(props.key.name))\n    .map((props) => props.value.name);\n}\n\n/**\n * Check whether or not a node is a empty method.\n *\n * @param {Object} node The node to check.\n * @returns {boolean} Whether or not the node is an empty method.\n */\nfunction isEmptyMethod(node) {\n  return node.value.body && node.value.body.body && node.value.body.body.length <= 0;\n}\n\n/**\n * Travels up the ancestors of a given node, if the predicate function returns\n * truthy for a given node or ancestor, return that node, otherwise return null\n *\n * @name getAncestor\n * @param {Object} node The child node to start at\n * @param {Function} predicate Function that should return a boolean for a given value\n * @returns {Object|null} The first node that matches predicate, otherwise null\n */\nfunction getAncestor(node, predicate) {\n  let currentNode = node;\n  while (currentNode) {\n    if (predicate(currentNode)) {\n      return currentNode;\n    }\n\n    currentNode = currentNode.parent;\n  }\n\n  return null;\n}\n\n/**\n * Examples:\n * x -> x\n * x() -> x\n * x.y -> x.y\n * x.y() -> x.y\n *\n * @param {node} node\n * @param {boolean=false} includeFunctionCallParens - whether to include parenthesis () to distinguish function calls from objects/properties\n * @returns {string}\n */\nfunction getName(node, includeFunctionCallParens = false) {\n  if (isIdentifier(node)) {\n    return node.name;\n  } else if (isCallExpression(node) || isOptionalCallExpression(node)) {\n    return `${getName(node.callee, includeFunctionCallParens)}${\n      includeFunctionCallParens ? '()' : ''\n    }`;\n  } else if (isMemberExpression(node) || isOptionalMemberExpression(node)) {\n    return `${getName(node.object, includeFunctionCallParens)}.${getName(\n      node.property,\n      includeFunctionCallParens\n    )}`;\n  } else if (node.type === 'ChainExpression') {\n    return getName(node.expression, includeFunctionCallParens);\n  }\n  return undefined;\n}\n\n/**\n * Return the passed in node, or if the node is a variable, return the value of this variable.\n *\n * Example:\n *   Calling the function on `{ foo: true }` will return the same node.\n * Example:\n *   const x = { foo: true };\n *   Calling the function on `x` will return the ObjectExpression value.\n *\n * @param {Node} node\n * @param {ScopeManager} scopeManager\n * @returns {Node | null}\n */\nfunction getNodeOrNodeFromVariable(node, scopeManager) {\n  if (node.type === 'Identifier') {\n    // Find the definition of this variable.\n    const variable = findVariable(scopeManager.acquire(node) || scopeManager.globalScope, node);\n    if (\n      variable &&\n      variable.defs &&\n      variable.defs[0] &&\n      variable.defs[0].node &&\n      variable.defs[0].node.type === 'VariableDeclarator' &&\n      variable.defs[0].node.init\n    ) {\n      return variable.defs[0].node.init;\n    }\n  }\n\n  // If the node isn't a variable or we can't find the initialized value of it, just return the node itself.\n  return node;\n}\n\n/**\n * Check whether a node is inside the left side of an AssignmentExpression.\n *\n * Example:\n * - this.x.y = 123;\n * Nodes in the left side of this example:\n * - this\n * - x\n * - y\n *\n * @param {Node} node - node to check\n * @returns {boolean} - whether the node is inside the left side of the given AssignmentExpression\n */\nfunction isInLeftSideOfAssignmentExpression(node) {\n  return Boolean(\n    getAncestor(\n      node,\n      (ancestor) =>\n        ancestor.parent &&\n        isAssignmentExpression(ancestor.parent) &&\n        ancestor === ancestor.parent.left\n    )\n  );\n}\n\nfunction startsWithThisExpression(node) {\n  if (isCallExpression(node) && node.callee) {\n    return startsWithThisExpression(node.callee);\n  } else if (isMemberExpression(node) && node.object) {\n    return startsWithThisExpression(node.object);\n  } else if (isIdentifier(node)) {\n    return false;\n  } else if (isThisExpression(node)) {\n    return true;\n  }\n  return false;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"eslint-plugin-ember\",\n  \"version\": \"13.2.1\",\n  \"description\": \"ESLint plugin for Ember.js apps\",\n  \"keywords\": [\n    \"eslint\",\n    \"eslintplugin\",\n    \"eslint-plugin\",\n    \"ember\",\n    \"ember.js\",\n    \"plugin\",\n    \"styleguide\",\n    \"rules\"\n  ],\n  \"homepage\": \"https://github.com/ember-cli/eslint-plugin-ember#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ember-cli/eslint-plugin-ember/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ember-cli/eslint-plugin-ember.git\"\n  },\n  \"license\": \"MIT\",\n  \"exports\": {\n    \".\": \"./lib/index.js\",\n    \"./recommended\": {\n      \"import\": \"./lib/recommended.mjs\"\n    },\n    \"./configs/*\": \"./lib/config/*.js\"\n  },\n  \"main\": \"./lib/index.js\",\n  \"directories\": {\n    \"test\": \"test\",\n    \"rules\": \"rules\"\n  },\n  \"files\": [\n    \"lib\"\n  ],\n  \"scripts\": {\n    \"bench\": \"./scripts/run-bench.sh tests/lint.bench.mjs\",\n    \"bench:compare\": \"node scripts/bench-compare.mjs\",\n    \"bench:summary\": \"./scripts/local-bench-summary.sh\",\n    \"format\": \"prettier . --write\",\n    \"lint\": \"npm-run-all --continue-on-error --aggregate-output --parallel \\\"lint:!(fix)\\\"\",\n    \"lint:docs\": \"markdownlint \\\"**/*.md\\\"\",\n    \"lint:docs:fix\": \"pnpm lint:docs --fix\",\n    \"lint:eslint-docs\": \"pnpm update:eslint-docs --check\",\n    \"lint:eslint-docs:fix\": \"npm-run-all \\\"update:eslint-docs\\\"\",\n    \"lint:fix\": \"npm-run-all \\\"lint:*:fix\\\" && pnpm format\",\n    \"lint:js\": \"eslint \\\"./{lib,docs,tests}/**/*\\\"\",\n    \"lint:js:fix\": \"pnpm lint:js --fix\",\n    \"lint:package-json\": \"npmPkgJsonLint .\",\n    \"lint:package-json-sorting\": \"sort-package-json --check\",\n    \"lint:package-json-sorting:fix\": \"sort-package-json package.json\",\n    \"lint:prettier\": \"prettier . --check\",\n    \"lint:remote\": \"eslint-remote-tester\",\n    \"start\": \"pnpm test:watch\",\n    \"test\": \"vitest run\",\n    \"test:coverage\": \"vitest --coverage\",\n    \"test:watch\": \"vitest\",\n    \"update\": \"node ./scripts/update-rules.js && node ./scripts/list-jquery-methods.js && npm-run-all update:eslint-docs\",\n    \"update:eslint-docs\": \"eslint-doc-generator\"\n  },\n  \"dependencies\": {\n    \"@ember-data/rfc395-data\": \"^0.0.4\",\n    \"aria-query\": \"^5.3.2\",\n    \"axobject-query\": \"^4.1.0\",\n    \"css-tree\": \"^3.0.1\",\n    \"editorconfig\": \"^3.0.2\",\n    \"ember-eslint-parser\": \"^0.11.3\",\n    \"ember-rfc176-data\": \"^0.3.18\",\n    \"eslint-utils\": \"^3.0.0\",\n    \"estraverse\": \"^5.3.0\",\n    \"html-tags\": \"^3.3.1\",\n    \"language-tags\": \"^1.0.9\",\n    \"lodash.camelcase\": \"^4.3.0\",\n    \"lodash.kebabcase\": \"^4.1.1\",\n    \"mathml-tag-names\": \"^4.0.0\",\n    \"requireindex\": \"^1.2.0\",\n    \"snake-case\": \"^3.0.3\",\n    \"svg-tags\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.25.9\",\n    \"@babel/eslint-parser\": \"^7.22.15\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n    \"@babel/plugin-proposal-decorators\": \"^7.23.2\",\n    \"@eslint/compat\": \"^2.0.1\",\n    \"@eslint/eslintrc\": \"^3.0.1\",\n    \"@eslint/js\": \"^9.19.0\",\n    \"@types/eslint\": \"^8.44.6\",\n    \"@typescript-eslint/parser\": \"^8.11.0\",\n    \"@vitest/coverage-v8\": \"^2.1.3\",\n    \"eslint\": \"^8.55.0\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-doc-generator\": \"^2.1.2\",\n    \"eslint-plugin-eslint-comments\": \"^3.2.0\",\n    \"eslint-plugin-eslint-plugin\": \"^5.1.1\",\n    \"eslint-plugin-filenames\": \"^1.3.2\",\n    \"eslint-plugin-import\": \"^2.29.0\",\n    \"eslint-plugin-markdown\": \"^5.1.0\",\n    \"eslint-plugin-n\": \"^17.11.1\",\n    \"eslint-plugin-unicorn\": \"^51.0.0\",\n    \"eslint-remote-tester\": \"^3.0.1\",\n    \"globals\": \"^16.4.0\",\n    \"jquery\": \"^3.7.1\",\n    \"jsdom\": \"^24.0.0\",\n    \"markdownlint-cli\": \"^0.48.0\",\n    \"mitata\": \"^1.0.34\",\n    \"npm-package-json-lint\": \"^7.0.0\",\n    \"npm-run-all2\": \"^5.0.0\",\n    \"prettier\": \"^3.0.3\",\n    \"release-plan\": \"^0.18.0\",\n    \"sort-package-json\": \"^2.6.0\",\n    \"typescript\": \"^5.2.2\",\n    \"typescript-eslint\": \"^8.7.0\",\n    \"vite\": \"^8.0.5\",\n    \"vitest\": \"^2.1.3\"\n  },\n  \"peerDependencies\": {\n    \"@typescript-eslint/parser\": \"*\",\n    \"eslint\": \">= 8.40.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"@typescript-eslint/parser\": {\n      \"optional\": true\n    }\n  },\n  \"packageManager\": \"pnpm@10.33.2\",\n  \"engines\": {\n    \"node\": \">= 20.19\"\n  },\n  \"volta\": {\n    \"node\": \"24.15.0\",\n    \"pnpm\": \"10.33.2\"\n  },\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org\"\n  }\n}\n"
  },
  {
    "path": "scripts/bench-compare.mjs",
    "content": "/* eslint-disable n/no-process-exit */\n/**\n * Benchmark comparison script using mitata.\n *\n * Copies the base branch's source to a temp directory, installs its\n * dependencies, then runs the mitata bench script with --control-dir so that\n * both control (base) and experiment (current) plugins are benchmarked in the\n * same process — giving mitata a fair, head-to-head comparison with built-in\n * summary tables and boxplots.\n *\n * Usage:\n *   node scripts/bench-compare.mjs [--base <branch>]\n *\n * Options:\n *   --base <branch>   Branch to compare against (default: master)\n */\n\nimport { execSync, spawnSync } from 'node:child_process';\nimport { existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\n\n// ---------------------------------------------------------------------------\n// CLI args\n// ---------------------------------------------------------------------------\n\nconst args = process.argv.slice(2);\nconst baseIdx = args.indexOf('--base');\nconst BASE_BRANCH = baseIdx !== -1 ? args[baseIdx + 1] : 'master';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction run(cmd, opts = {}) {\n  return execSync(cmd, { stdio: 'inherit', ...opts });\n}\n\n/**\n * Resolve a branch name to a commit SHA. Tries `origin/<branch>` first (for CI\n * where only the PR branch is checked out locally), then falls back to `<branch>`.\n */\nfunction resolveRef(branch) {\n  for (const candidate of [`origin/${branch}`, branch]) {\n    const result = spawnSync('git', ['rev-parse', '--verify', candidate], {\n      encoding: 'utf8',\n      stdio: ['pipe', 'pipe', 'pipe'],\n    });\n    if (result.status === 0) return result.stdout.trim();\n  }\n  throw new Error(`Could not resolve ref for branch \"${branch}\". Is it fetched?`);\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nconst ROOT = process.cwd();\nconst CONTROL_DIR = join(tmpdir(), `bench-control-${BASE_BRANCH}-${Date.now()}`);\n\nconsole.error(`\\n🔧  Setting up control (${BASE_BRANCH}) in ${CONTROL_DIR}\\n`);\n\nconst BASE_REF = resolveRef(BASE_BRANCH);\nconsole.error(`   Resolved ${BASE_BRANCH} → ${BASE_REF.slice(0, 10)}\\n`);\n\n// Clean up temp dir on exit\nfunction cleanup() {\n  if (existsSync(CONTROL_DIR)) {\n    try {\n      rmSync(CONTROL_DIR, { recursive: true, force: true });\n    } catch {\n      // best-effort cleanup\n    }\n  }\n}\nprocess.on('exit', cleanup);\nprocess.on('SIGINT', () => process.exit(130));\nprocess.on('SIGTERM', () => process.exit(143));\n\ntry {\n  // ── 1. Export base branch source to temp dir ─────────────────────────────\n  mkdirSync(CONTROL_DIR, { recursive: true });\n\n  // Copy package manifests and source (use resolved SHA for reliability)\n  run(\n    `git archive ${BASE_REF} -- package.json pnpm-lock.yaml .npmrc lib/ | tar -x -C \"${CONTROL_DIR}\"`\n  );\n\n  // ── 2. Install dependencies in control dir ───────────────────────────────\n  console.error(`\\n📦  Installing dependencies for control (${BASE_BRANCH})…\\n`);\n  try {\n    run('pnpm install --frozen-lockfile', {\n      cwd: CONTROL_DIR,\n      stdio: ['inherit', 'pipe', 'inherit'],\n    });\n  } catch {\n    console.error('⚠️  Frozen install failed, retrying without --frozen-lockfile…\\n');\n    run('pnpm install --no-frozen-lockfile', {\n      cwd: CONTROL_DIR,\n      stdio: ['inherit', 'pipe', 'inherit'],\n    });\n  }\n\n  // ── 3. Run mitata bench with --control-dir ───────────────────────────────\n  console.error(`\\n🏎️  Running benchmarks (experiment vs control)…\\n`);\n\n  const benchScript = join(ROOT, 'tests/lint.bench.mjs');\n  const benchArgs = [\n    '--expose-gc',\n    '--max-old-space-size=4096',\n    benchScript,\n    '--control-dir',\n    CONTROL_DIR,\n  ];\n\n  // CPU pinning on Linux to reduce cross-core migration variance\n  const IS_LINUX = process.platform === 'linux';\n  const HAS_TASKSET = IS_LINUX && spawnSync('which', ['taskset'], { stdio: 'pipe' }).status === 0;\n\n  let cmd = 'node';\n  let fullArgs = benchArgs;\n\n  if (HAS_TASKSET) {\n    cmd = 'taskset';\n    fullArgs = ['-c', '0', 'node', ...benchArgs];\n    console.error('📌  CPU pinning enabled (taskset -c 0)\\n');\n  }\n\n  const result = spawnSync(cmd, fullArgs, {\n    stdio: 'inherit',\n    cwd: ROOT,\n    env: { ...process.env },\n  });\n\n  if (result.status !== 0) {\n    console.error('\\n❌  Benchmark run failed.');\n    process.exit(1);\n  }\n\n  console.error('\\n✅  Benchmark comparison complete.\\n');\n} catch (e) {\n  console.error('❌  Error:', e.message);\n  process.exit(1);\n}\n"
  },
  {
    "path": "scripts/bench-utils.mjs",
    "content": "/**\n * Shared utilities for benchmark formatting scripts.\n */\n\nimport { readFileSync } from 'node:fs';\n\nexport function formatTime(ns) {\n  if (ns >= 1e6) return `${(ns / 1e6).toFixed(2)} ms`;\n  if (ns >= 1e3) return `${(ns / 1e3).toFixed(2)} µs`;\n  return `${ns.toFixed(2)} ns`;\n}\n\nexport function deltaEmoji(pct) {\n  const abs = Math.abs(pct);\n  if (abs < 2) return '⚪';\n  if (pct <= -5) return '🟢';\n  if (pct >= 5) return '🔴';\n  if (pct < 0) return '🟢';\n  return '🟠';\n}\n\n/**\n * Parse benchmark JSON results into control/experiment pairs with deltas.\n * Uses p50 (median) which is more robust to outliers than avg.\n */\nexport function parsePairs(json) {\n  const pairs = new Map();\n\n  for (const trial of json.benchmarks || []) {\n    for (const r of trial.runs || []) {\n      if (!r.stats) continue;\n      const m = r.name.match(/^(.+)\\s+\\((control|experiment)\\)$/);\n      if (!m) continue;\n      const [, key, role] = m;\n      if (!pairs.has(key)) pairs.set(key, {});\n      pairs.get(key)[role] = r.stats;\n    }\n  }\n\n  const rows = [];\n  for (const [name, { control, experiment }] of pairs) {\n    if (!control || !experiment) continue;\n    const ctrlVal = control.p50 ?? control.avg;\n    const expVal = experiment.p50 ?? experiment.avg;\n    const delta = ((expVal - ctrlVal) / ctrlVal) * 100;\n    rows.push({ name, control: ctrlVal, experiment: expVal, delta });\n  }\n\n  return rows;\n}\n\n/**\n * Read and parse the benchmark JSON results file.\n */\nexport function readBenchJSON(path) {\n  return JSON.parse(readFileSync(path, 'utf8'));\n}\n"
  },
  {
    "path": "scripts/format-bench-cli.mjs",
    "content": "/* eslint-disable n/no-process-exit */\n/**\n * Format benchmark JSON results as a CLI-friendly summary table.\n *\n * Environment variables:\n *   BENCH_JSON_OUTPUT - Path to the JSON bench results\n */\n\nimport { formatTime, deltaEmoji, parsePairs, readBenchJSON } from './bench-utils.mjs';\n\nconst jsonPath = process.env.BENCH_JSON_OUTPUT;\n\nif (!jsonPath) {\n  console.error('BENCH_JSON_OUTPUT not set');\n  process.exit(1);\n}\n\nlet json;\n\ntry {\n  json = readBenchJSON(jsonPath);\n} catch (e) {\n  console.error(`Could not read ${jsonPath}: ${e.message}`);\n  process.exit(1);\n}\n\nconst rows = parsePairs(json);\n\nif (rows.length === 0) {\n  console.log('No comparison data found.');\n  process.exit(0);\n}\n\n// Calculate column widths\nconst nameW = Math.max('Benchmark'.length, ...rows.map((r) => r.name.length));\nconst ctrlW = Math.max('Control (p50)'.length, ...rows.map((r) => formatTime(r.control).length));\nconst expW = Math.max(\n  'Experiment (p50)'.length,\n  ...rows.map((r) => formatTime(r.experiment).length)\n);\nconst deltaW = Math.max(\n  'Δ'.length,\n  ...rows.map((r) => {\n    const sign = r.delta > 0 ? '+' : '';\n    return `${sign}${r.delta.toFixed(1)}%`.length;\n  })\n);\n\n// Print table\nconst pad = (s, w, right) => (right ? s.padStart(w) : s.padEnd(w));\n\nconsole.log();\nconsole.log(\n  `   ${pad('Benchmark', nameW)}   ${pad('Control (p50)', ctrlW, true)}   ${pad('Experiment (p50)', expW, true)}   ${pad('Δ', deltaW, true)}`\n);\nconsole.log(\n  `   ${'─'.repeat(nameW)}   ${'─'.repeat(ctrlW)}   ${'─'.repeat(expW)}   ${'─'.repeat(deltaW)}`\n);\n\nfor (const row of rows) {\n  const emoji = deltaEmoji(row.delta);\n  const sign = row.delta > 0 ? '+' : '';\n  const deltaStr = `${sign}${row.delta.toFixed(1)}%`;\n\n  console.log(\n    `${emoji} ${pad(row.name, nameW)}   ${pad(formatTime(row.control), ctrlW, true)}   ${pad(formatTime(row.experiment), expW, true)}   ${pad(deltaStr, deltaW, true)}`\n  );\n}\n\nconsole.log();\nconsole.log('🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%');\nconsole.log();\n"
  },
  {
    "path": "scripts/format-bench-comment.mjs",
    "content": "/**\n * Format benchmark comparison results into a GitHub PR comment.\n *\n * Reads the plain-text mitata output and (optionally) the JSON results from\n * the bench run, then produces a GitHub-flavored markdown comment with:\n *   1. A summary table (when comparison data is available)\n *   2. Full mitata output in a collapsible <details> section\n *\n * Environment variables:\n *   BENCH_OUTPUT_FILE   - Path to the plain-text bench output\n *   BENCH_JSON_OUTPUT   - Path to the JSON bench results (optional)\n *   BENCH_JOB_SUCCESS   - Set to \"true\" if the benchmark job succeeded\n */\n\nimport { readFileSync } from 'node:fs';\nimport { formatTime, deltaEmoji, parsePairs, readBenchJSON } from './bench-utils.mjs';\n\nconst marker = '<!-- bench-compare -->';\n\n// ---------------------------------------------------------------------------\n// Read raw mitata output\n// ---------------------------------------------------------------------------\n\nlet rawOutput;\ntry {\n  rawOutput = readFileSync(process.env.BENCH_OUTPUT_FILE, 'utf8').trim();\n} catch {\n  console.warn('Warning: could not read BENCH_OUTPUT_FILE; using placeholder text.');\n  rawOutput = '(no output — benchmark may have failed to start)';\n}\n\n// Strip any lines before the mitata header (safety net for leaked setup messages)\nconst benchStart = rawOutput.search(/^(clk:|benchmark\\b)/m);\nif (benchStart > 0) {\n  rawOutput = rawOutput.slice(benchStart);\n}\n\n// ---------------------------------------------------------------------------\n// Read JSON results (if available) and build summary\n// ---------------------------------------------------------------------------\n\nlet summarySection = '';\nconst jsonPath = process.env.BENCH_JSON_OUTPUT;\n\nif (jsonPath) {\n  try {\n    const rows = parsePairs(readBenchJSON(jsonPath));\n\n    if (rows.length > 0) {\n      const tableRows = rows.map(({ name, control, experiment, delta }) => {\n        const emoji = deltaEmoji(delta);\n        const sign = delta > 0 ? '+' : '';\n        return `| ${emoji} | ${name} | ${formatTime(control)} | ${formatTime(experiment)} | ${sign}${delta.toFixed(1)}% |`;\n      });\n\n      summarySection = [\n        '',\n        '| | Benchmark | Control (p50) | Experiment (p50) | Δ |',\n        '|---|---|---:|---:|---:|',\n        ...tableRows,\n        '',\n        '> 🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%',\n        '',\n      ].join('\\n');\n    }\n  } catch {\n    // JSON not available or malformed — skip summary\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Assemble comment\n// ---------------------------------------------------------------------------\n\nconst success = process.env.BENCH_JOB_SUCCESS === 'true';\nconst heading = success ? '## 🏎️ Benchmark Comparison' : '## ❌ Benchmark Comparison (failed)';\n\nconst body = [\n  marker,\n  heading,\n  summarySection,\n  '<details>',\n  '<summary>Full mitata output</summary>',\n  '',\n  '```',\n  rawOutput,\n  '```',\n  '',\n  '</details>',\n].join('\\n');\n\nprocess.stdout.write(body + '\\n');\n"
  },
  {
    "path": "scripts/list-jquery-methods.js",
    "content": "/**\n * Based on work from wikimedia/eslint-plugin-no-jquery\n *\n * https://github.com/wikimedia/eslint-plugin-no-jquery/blob/351eca834e002a056f71072af412bb19255157ce/tools/build-all-methods.js\n */\n\n'use strict';\n\nconst fs = require('fs');\nconst { JSDOM } = require('jsdom');\n\nconst { window } = new JSDOM('');\nconst $ = require('jquery')(window);\n\nconst allMethods = Object.keys($)\n  .filter((k) => !k.startsWith('_') && typeof $[k] === 'function')\n  .sort()\n  .map((k) => `  '${k}',`)\n  .join('\\n');\n\nfs.writeFile(\n  'lib/utils/jquery-methods.js',\n  `${\n    '// This file is built by scripts/list-jquery-methods.js; do not edit it directly.\\n' +\n    'module.exports = [\\n'\n  }${allMethods}\\n];\\n`,\n  (err) => {\n    if (err) {\n      throw err;\n    }\n  }\n);\n"
  },
  {
    "path": "scripts/local-bench-summary.sh",
    "content": "#!/usr/bin/env bash\n\nexport BENCH_JSON_OUTPUT=./bench-results.json\n\npnpm bench:compare\n\necho \"\"\necho \"━━━ Summary ━━━\"\nnode scripts/format-bench-cli.mjs\n\n# Print tips for reducing variance\necho \"━━━ Tips for more reliable results ━━━\"\necho \"\"\n\ntips=()\n\nif [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then\n  gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)\n  if [ \"$gov\" != \"performance\" ]; then\n    tips+=(\"CPU governor is '$gov' — set to 'performance' for fixed frequency:\")\n    tips+=(\"  sudo cpupower frequency-set -g performance\")\n    tips+=(\"\")\n  fi\nfi\n\nif [ -f /sys/devices/system/cpu/cpufreq/boost ]; then\n  boost=$(cat /sys/devices/system/cpu/cpufreq/boost)\n  if [ \"$boost\" = \"1\" ]; then\n    tips+=(\"CPU boost is enabled — disable to prevent thermal-dependent frequency:\")\n    tips+=(\"  echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost\")\n    tips+=(\"\")\n  fi\nelif [ -f /sys/devices/system/cpu/intel_pstate/no_turbo ]; then\n  no_turbo=$(cat /sys/devices/system/cpu/intel_pstate/no_turbo)\n  if [ \"$no_turbo\" = \"0\" ]; then\n    tips+=(\"Intel Turbo Boost is enabled — disable to prevent thermal-dependent frequency:\")\n    tips+=(\"  echo 1 | sudo tee /sys/devices/system/cpu/intel_pstate/no_turbo\")\n    tips+=(\"\")\n  fi\nfi\n\ntips+=(\"Close other applications to reduce CPU contention\")\ntips+=(\"Run multiple times — if deltas flip sign between runs, they're noise\")\n\nfor tip in \"${tips[@]}\"; do\n  echo \"  $tip\"\ndone\necho \"\"\n"
  },
  {
    "path": "scripts/run-bench.sh",
    "content": "#!/usr/bin/env bash\n#\n# Wrapper that runs a node command with CPU pinning when available.\n#\n# Usage: ./scripts/run-bench.sh <node args...>\n\nset -euo pipefail\n\nCMD=(node --expose-gc \"$@\")\n\n# CPU pinning on Linux — keep the process on a single core\nif command -v taskset &>/dev/null; then\n  CMD=(taskset -c 0 \"${CMD[@]}\")\n  echo \"📌  CPU pinning enabled (taskset -c 0)\" >&2\n  echo \"\" >&2\nfi\n\nexec \"${CMD[@]}\"\n"
  },
  {
    "path": "scripts/update-rules.js",
    "content": "/**\n * This is a modified file that originally was created by:\n * @author Toru Nagashima\n * @copyright 2017 Toru Nagashima. All rights reserved.\n * See LICENSE file in root directory for full license.\n */\n\n'use strict';\n\n// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst fs = require('fs');\nconst path = require('path');\n\n// ------------------------------------------------------------------------------\n// Main\n// ------------------------------------------------------------------------------\n\nfunction generate(filename, filter) {\n  const root = path.resolve(__dirname, '../lib/rules');\n  const recommendedRulesFile = path.resolve(__dirname, filename);\n\n  const rules = fs\n    .readdirSync(root)\n    .filter((file) => path.extname(file) === '.js')\n    .map((file) => path.basename(file, '.js'))\n    .map((fileName) => [fileName, require(path.join(root, fileName))]); // eslint-disable-line import/no-dynamic-require\n\n  const recommendedRules = rules.reduce((obj, entry) => {\n    const name = `ember/${entry[0]}`;\n    if (filter(entry)) {\n      obj[name] = 'error'; // eslint-disable-line no-param-reassign\n    }\n    return obj;\n  }, {});\n\n  const recommendedRulesContent = `/*\n * IMPORTANT!\n * This file has been automatically generated.\n * In order to update its content based on rules'\n * definitions, execute \"npm run update\"\n */\nmodule.exports = ${JSON.stringify(recommendedRules, null, 2)};\n`;\n\n  fs.writeFileSync(recommendedRulesFile, recommendedRulesContent);\n}\n\ngenerate('../lib/recommended-rules.js', (entry) => entry[1].meta.docs.recommended);\ngenerate('../lib/recommended-rules-gjs.js', (entry) => entry[1].meta.docs.recommendedGjs);\ngenerate('../lib/recommended-rules-gts.js', (entry) => entry[1].meta.docs.recommendedGts);\n"
  },
  {
    "path": "tests/__snapshots__/recommended.js.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`recommended rules > gjs config has the right list 1`] = `\n[\n  \"template-no-let-reference\",\n]\n`;\n\nexports[`recommended rules > gts config has the right list 1`] = `\n[\n  \"template-no-let-reference\",\n]\n`;\n\nexports[`recommended rules > has the right list 1`] = `\n[\n  \"avoid-leaking-state-in-ember-objects\",\n  \"avoid-using-needs-in-controllers\",\n  \"classic-decorator-hooks\",\n  \"classic-decorator-no-classic-methods\",\n  \"closure-actions\",\n  \"jquery-ember-run\",\n  \"new-module-imports\",\n  \"no-actions-hash\",\n  \"no-arrow-function-computed-properties\",\n  \"no-assignment-of-untracked-properties-used-in-tracking-contexts\",\n  \"no-at-ember-render-modifiers\",\n  \"no-attrs-in-components\",\n  \"no-attrs-snapshot\",\n  \"no-capital-letters-in-routes\",\n  \"no-classic-classes\",\n  \"no-classic-components\",\n  \"no-component-lifecycle-hooks\",\n  \"no-computed-properties-in-native-classes\",\n  \"no-controller-access-in-routes\",\n  \"no-deeply-nested-dependent-keys-with-each\",\n  \"no-deprecated-router-transition-methods\",\n  \"no-duplicate-dependent-keys\",\n  \"no-ember-super-in-es-classes\",\n  \"no-ember-testing-in-module-scope\",\n  \"no-empty-glimmer-component-classes\",\n  \"no-function-prototype-extensions\",\n  \"no-get-with-default\",\n  \"no-get\",\n  \"no-global-jquery\",\n  \"no-implicit-injections\",\n  \"no-incorrect-calls-with-inline-anonymous-functions\",\n  \"no-incorrect-computed-macros\",\n  \"no-invalid-debug-function-arguments\",\n  \"no-invalid-dependent-keys\",\n  \"no-invalid-test-waiters\",\n  \"no-jquery\",\n  \"no-legacy-test-waiters\",\n  \"no-mixins\",\n  \"no-new-mixins\",\n  \"no-noop-setup-on-error-in-before\",\n  \"no-observers\",\n  \"no-old-shims\",\n  \"no-on-calls-in-components\",\n  \"no-pause-test\",\n  \"no-private-routing-service\",\n  \"no-restricted-resolver-tests\",\n  \"no-runloop\",\n  \"no-settled-after-test-helper\",\n  \"no-shadow-route-definition\",\n  \"no-side-effects\",\n  \"no-string-prototype-extensions\",\n  \"no-test-and-then\",\n  \"no-test-import-export\",\n  \"no-test-module-for\",\n  \"no-test-support-import\",\n  \"no-test-this-render\",\n  \"no-tracked-properties-from-args\",\n  \"no-try-invoke\",\n  \"no-unnecessary-route-path-option\",\n  \"no-volatile-computed-properties\",\n  \"prefer-ember-test-helpers\",\n  \"require-computed-macros\",\n  \"require-computed-property-dependencies\",\n  \"require-return-from-computed\",\n  \"require-super-in-lifecycle-hooks\",\n  \"require-tagless-components\",\n  \"require-valid-css-selector-in-test-helpers\",\n  \"routes-segments-snake-case\",\n  \"use-brace-expansion\",\n  \"use-ember-data-rfc-395-imports\",\n]\n`;\n"
  },
  {
    "path": "tests/bench/large.gjs",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { on } from '@ember/modifier';\nimport { fn } from '@ember/helper';\nimport { inject as service } from '@ember/service';\n\n// ── Presentational Components ─────────────────────────────────────────────\n\nconst Avatar = <template>\n  <span class=\"avatar avatar--{{if @size @size 'md'}}\" aria-label={{@user.name}}>\n    {{get @user.name 0}}\n  </span>\n</template>;\n\nconst Badge = <template>\n  <span class=\"badge badge--{{@variant}}\">{{yield}}</span>\n</template>;\n\nconst StatusDot = <template>\n  <span class=\"status-dot status-dot--{{@status}}\" title={{@status}}></span>\n</template>;\n\nconst PriorityIcon = <template>\n  <span class=\"priority-icon priority-icon--{{@level}}\">\n    {{#if (eq @level \"critical\")}}⬆⬆{{/if}}\n    {{#if (eq @level \"high\")}}⬆{{/if}}\n    {{#if (eq @level \"medium\")}}➡{{/if}}\n    {{#if (eq @level \"low\")}}⬇{{/if}}\n  </span>\n</template>;\n\nconst ProgressBar = <template>\n  <div class=\"progress\" role=\"progressbar\" aria-valuenow={{@value}} aria-valuemin=\"0\" aria-valuemax=\"100\">\n    <div class=\"progress__bar progress__bar--{{@color}}\" style=\"width: {{@value}}%\"></div>\n    {{#if @showLabel}}\n      <span class=\"progress__label\">{{@value}}%</span>\n    {{/if}}\n  </div>\n</template>;\n\nconst Tooltip = <template>\n  <span class=\"tooltip\" data-tooltip={{@text}}>{{yield}}</span>\n</template>;\n\nconst EmptyState = <template>\n  <div class=\"empty-state\">\n    <div class=\"empty-state__icon\">{{@icon}}</div>\n    <h3>{{@title}}</h3>\n    <p>{{@description}}</p>\n    {{#if @actionLabel}}\n      <button type=\"button\" class=\"btn btn--primary\" {{on \"click\" @onAction}}>\n        {{@actionLabel}}\n      </button>\n    {{/if}}\n  </div>\n</template>;\n\nconst LoadingSpinner = <template>\n  <div class=\"spinner {{if @size (concat 'spinner--' @size)}}\" role=\"status\">\n    <span class=\"sr-only\">Loading…</span>\n  </div>\n</template>;\n\nconst Pagination = <template>\n  <nav class=\"pagination\" aria-label=\"Pagination\">\n    <button\n      type=\"button\"\n      class=\"pagination__btn\"\n      disabled={{not @hasPrevious}}\n      {{on \"click\" @onPrevious}}\n    >\n      ← Previous\n    </button>\n    <span class=\"pagination__info\">\n      Page {{@current}} of {{@total}}\n    </span>\n    <button\n      type=\"button\"\n      class=\"pagination__btn\"\n      disabled={{not @hasNext}}\n      {{on \"click\" @onNext}}\n    >\n      Next →\n    </button>\n  </nav>\n</template>;\n\n// ── Card Components ───────────────────────────────────────────────────────\n\nconst UserCard = <template>\n  <article class=\"user-card {{if @user.active '' 'user-card--inactive'}}\">\n    <div class=\"user-card__header\">\n      <Avatar @user={{@user}} @size=\"lg\" />\n      <StatusDot @status={{if @user.active \"online\" \"offline\"}} />\n    </div>\n    <div class=\"user-card__body\">\n      <strong class=\"user-card__name\">{{@user.name}}</strong>\n      <span class=\"user-card__email\">{{@user.email}}</span>\n      <Badge @variant={{@user.role}}>{{@user.role}}</Badge>\n      {{#if @user.department}}\n        <span class=\"user-card__dept\">{{@user.department}}</span>\n      {{/if}}\n    </div>\n    <div class=\"user-card__footer\">\n      <Tooltip @text=\"View profile\">\n        <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" (fn @onSelect @user)}}>\n          View\n        </button>\n      </Tooltip>\n      {{#if @onEdit}}\n        <button type=\"button\" class=\"btn btn--sm btn--outline\" {{on \"click\" (fn @onEdit @user)}}>\n          Edit\n        </button>\n      {{/if}}\n      {{#if @onToggleStatus}}\n        <button\n          type=\"button\"\n          class=\"btn btn--sm {{if @user.active 'btn--warning' 'btn--success'}}\"\n          {{on \"click\" (fn @onToggleStatus @user)}}\n        >\n          {{if @user.active \"Deactivate\" \"Activate\"}}\n        </button>\n      {{/if}}\n    </div>\n  </article>\n</template>;\n\nconst TaskCard = <template>\n  <div class=\"task-card task-card--{{@task.status}} {{if @isDragging 'task-card--dragging'}}\">\n    <div class=\"task-card__header\">\n      <PriorityIcon @level={{@task.priority}} />\n      <span class=\"task-card__id\">#{{@task.id}}</span>\n      {{#if @task.labels}}\n        <div class=\"task-card__labels\">\n          {{#each @task.labels as |label|}}\n            <Badge @variant=\"label\">{{label}}</Badge>\n          {{/each}}\n        </div>\n      {{/if}}\n    </div>\n    <h3 class=\"task-card__title\">{{@task.title}}</h3>\n    {{#if @task.description}}\n      <p class=\"task-card__desc\">{{@task.description}}</p>\n    {{/if}}\n    <div class=\"task-card__meta\">\n      {{#if @assignee}}\n        <Tooltip @text={{@assignee.name}}>\n          <Avatar @user={{@assignee}} @size=\"sm\" />\n        </Tooltip>\n      {{/if}}\n      {{#if @task.dueDate}}\n        <span class=\"task-card__due {{if @task.overdue 'task-card__due--late'}}\">\n          {{@task.dueDate}}\n        </span>\n      {{/if}}\n      {{#if @task.estimate}}\n        <span class=\"task-card__estimate\">{{@task.estimate}}h</span>\n      {{/if}}\n      {{#if @task.commentCount}}\n        <span class=\"task-card__comments\">💬 {{@task.commentCount}}</span>\n      {{/if}}\n    </div>\n    <div class=\"task-card__actions\">\n      <select {{on \"change\" (fn @onStatusChange @task)}}>\n        <option value=\"backlog\" selected={{eq @task.status \"backlog\"}}>Backlog</option>\n        <option value=\"todo\" selected={{eq @task.status \"todo\"}}>To Do</option>\n        <option value=\"in-progress\" selected={{eq @task.status \"in-progress\"}}>In Progress</option>\n        <option value=\"review\" selected={{eq @task.status \"review\"}}>Review</option>\n        <option value=\"done\" selected={{eq @task.status \"done\"}}>Done</option>\n      </select>\n      <button type=\"button\" class=\"btn btn--icon\" {{on \"click\" (fn @onEdit @task)}}>Edit</button>\n      <button type=\"button\" class=\"btn btn--icon btn--danger\" {{on \"click\" (fn @onDelete @task)}}>Delete</button>\n    </div>\n  </div>\n</template>;\n\nconst PostSummary = <template>\n  <article class=\"post-summary\">\n    <div class=\"post-summary__header\">\n      <h2 class=\"post-summary__title\">{{@post.title}}</h2>\n      {{#if @post.featured}}\n        <Badge @variant=\"featured\">Featured</Badge>\n      {{/if}}\n      <Badge @variant={{@post.status}}>{{@post.status}}</Badge>\n    </div>\n    <p class=\"post-summary__meta\">\n      By <Avatar @user={{@author}} @size=\"sm\" /> {{@author.name}}\n      {{#if @post.publishedAt}}\n        · Published {{@post.publishedAt}}\n      {{else}}\n        · <em>Draft</em>\n      {{/if}}\n      · {{@post.readTime}} min read\n    </p>\n    <p class=\"post-summary__body\">{{@post.excerpt}}</p>\n    {{#if @post.tags.length}}\n      <ul class=\"post-summary__tags\">\n        {{#each @post.tags as |tag|}}\n          <li>\n            <Badge @variant=\"tag\">{{tag}}</Badge>\n          </li>\n        {{/each}}\n      </ul>\n    {{/if}}\n    <div class=\"post-summary__stats\">\n      <span>👁 {{@post.views}}</span>\n      <span>❤ {{@post.likes}}</span>\n      <span>💬 {{@post.commentCount}}</span>\n    </div>\n    <div class=\"post-summary__actions\">\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" (fn @onView @post)}}>\n        Read More\n      </button>\n      {{#if @canEdit}}\n        <button type=\"button\" class=\"btn btn--sm btn--outline\" {{on \"click\" (fn @onEdit @post)}}>\n          Edit\n        </button>\n      {{/if}}\n    </div>\n  </article>\n</template>;\n\nconst CommentThread = <template>\n  <div class=\"comment-thread\">\n    {{#each @comments as |comment|}}\n      <div class=\"comment {{if comment.isAuthor 'comment--own'}}\">\n        <Avatar @user={{comment.author}} @size=\"sm\" />\n        <div class=\"comment__body\">\n          <div class=\"comment__header\">\n            <strong>{{comment.author.name}}</strong>\n            <time>{{comment.createdAt}}</time>\n          </div>\n          <p>{{comment.body}}</p>\n          <div class=\"comment__actions\">\n            <button type=\"button\" class=\"btn btn--link\" {{on \"click\" (fn @onReply comment)}}>\n              Reply\n            </button>\n            {{#if comment.isAuthor}}\n              <button type=\"button\" class=\"btn btn--link\" {{on \"click\" (fn @onEdit comment)}}>\n                Edit\n              </button>\n              <button type=\"button\" class=\"btn btn--link btn--danger\" {{on \"click\" (fn @onDelete comment)}}>\n                Delete\n              </button>\n            {{/if}}\n          </div>\n          {{#if comment.replies.length}}\n            <div class=\"comment__replies\">\n              {{#each comment.replies as |reply|}}\n                <div class=\"comment comment--reply\">\n                  <Avatar @user={{reply.author}} @size=\"xs\" />\n                  <div class=\"comment__body\">\n                    <strong>{{reply.author.name}}</strong>\n                    <time>{{reply.createdAt}}</time>\n                    <p>{{reply.body}}</p>\n                  </div>\n                </div>\n              {{/each}}\n            </div>\n          {{/if}}\n        </div>\n      </div>\n    {{/each}}\n    <form class=\"comment-form\" {{on \"submit\" @onSubmitComment}}>\n      <textarea\n        placeholder=\"Write a comment…\"\n        value={{@newCommentBody}}\n        {{on \"input\" @onCommentInput}}\n      ></textarea>\n      <button type=\"submit\" class=\"btn btn--primary btn--sm\" disabled={{not @newCommentBody}}>\n        Post Comment\n      </button>\n    </form>\n  </div>\n</template>;\n\nconst ActivityFeed = <template>\n  <div class=\"activity-feed\">\n    <h3 class=\"activity-feed__title\">Recent Activity</h3>\n    {{#each @entries as |entry|}}\n      <div class=\"activity-feed__item\">\n        <Avatar @user={{entry.user}} @size=\"xs\" />\n        <div class=\"activity-feed__content\">\n          <span class=\"activity-feed__action\">\n            <strong>{{entry.user.name}}</strong>\n            {{entry.description}}\n          </span>\n          <time class=\"activity-feed__time\">{{entry.timestamp}}</time>\n        </div>\n      </div>\n    {{/each}}\n    {{#unless @entries.length}}\n      <p class=\"activity-feed__empty\">No recent activity</p>\n    {{/unless}}\n  </div>\n</template>;\n\nconst KanbanColumn = <template>\n  <div\n    class=\"kanban-column {{if @isDropTarget 'kanban-column--drop-target'}}\"\n    {{on \"dragover\" @onDragOver}}\n    {{on \"drop\" @onDrop}}\n  >\n    <div class=\"kanban-column__header\">\n      <h3>{{@title}}</h3>\n      <Badge @variant=\"count\">{{@tasks.length}}</Badge>\n    </div>\n    <div class=\"kanban-column__body\">\n      {{#each @tasks as |task|}}\n        <TaskCard\n          @task={{task}}\n          @assignee={{task.assignee}}\n          @onStatusChange={{@onStatusChange}}\n          @onEdit={{@onEdit}}\n          @onDelete={{@onDelete}}\n        />\n      {{/each}}\n      {{#unless @tasks.length}}\n        <p class=\"kanban-column__empty\">No tasks</p>\n      {{/unless}}\n    </div>\n  </div>\n</template>;\n\nconst StatsGrid = <template>\n  <div class=\"stats-grid\">\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.totalProjects}}</span>\n      <span class=\"stats-grid__label\">Projects</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.totalTasks}}</span>\n      <span class=\"stats-grid__label\">Tasks</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.completedTasks}}</span>\n      <span class=\"stats-grid__label\">Completed</span>\n    </div>\n    <div class=\"stats-grid__card stats-grid__card--warning\">\n      <span class=\"stats-grid__value\">{{@stats.overdueTasks}}</span>\n      <span class=\"stats-grid__label\">Overdue</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.teamSize}}</span>\n      <span class=\"stats-grid__label\">Team Members</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <ProgressBar @value={{@stats.progress}} @color=\"success\" @showLabel={{true}} />\n      <span class=\"stats-grid__label\">Overall Progress</span>\n    </div>\n  </div>\n</template>;\n\nconst FilterPanel = <template>\n  {{#if @visible}}\n    <div class=\"filter-panel\">\n      <div class=\"filter-panel__group\">\n        <label>Status</label>\n        <select {{on \"change\" @onStatusChange}}>\n          <option value=\"\">All Statuses</option>\n          <option value=\"backlog\">Backlog</option>\n          <option value=\"todo\">To Do</option>\n          <option value=\"in-progress\">In Progress</option>\n          <option value=\"review\">Review</option>\n          <option value=\"done\">Done</option>\n        </select>\n      </div>\n      <div class=\"filter-panel__group\">\n        <label>Priority</label>\n        <select {{on \"change\" @onPriorityChange}}>\n          <option value=\"\">All Priorities</option>\n          <option value=\"critical\">Critical</option>\n          <option value=\"high\">High</option>\n          <option value=\"medium\">Medium</option>\n          <option value=\"low\">Low</option>\n        </select>\n      </div>\n      <div class=\"filter-panel__group\">\n        <label>Assignee</label>\n        <select {{on \"change\" @onAssigneeChange}}>\n          <option value=\"\">All Members</option>\n          {{#each @members as |member|}}\n            <option value={{member.id}}>{{member.name}}</option>\n          {{/each}}\n        </select>\n      </div>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onClear}}>\n        Clear All Filters\n      </button>\n    </div>\n  {{/if}}\n</template>;\n\n// ── Main Component ────────────────────────────────────────────────────────\n\nexport default class Dashboard extends Component {\n  @service store;\n  @service router;\n  @service('notifications') notify;\n  @service session;\n\n  @tracked users = [];\n  @tracked tasks = [];\n  @tracked posts = [];\n  @tracked comments = [];\n  @tracked activityEntries = [];\n  @tracked selectedUser = null;\n  @tracked selectedTask = null;\n  @tracked searchQuery = '';\n  @tracked activeTab = 'overview';\n  @tracked isLoading = false;\n  @tracked currentPage = 1;\n  @tracked pageSize = 20;\n  @tracked showFilters = false;\n  @tracked filterStatus = null;\n  @tracked filterPriority = null;\n  @tracked filterAssignee = null;\n  @tracked newCommentBody = '';\n  @tracked viewMode = 'list';\n\n  get filteredUsers() {\n    if (!this.searchQuery) return this.users;\n    const q = this.searchQuery.toLowerCase();\n    return this.users.filter(\n      (u) => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q),\n    );\n  }\n\n  get filteredTasks() {\n    let result = this.tasks;\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter((t) => t.title.toLowerCase().includes(q));\n    }\n    if (this.filterStatus) result = result.filter((t) => t.status === this.filterStatus);\n    if (this.filterPriority) result = result.filter((t) => t.priority === this.filterPriority);\n    if (this.filterAssignee) result = result.filter((t) => t.assigneeId === this.filterAssignee);\n    return result;\n  }\n\n  get tasksByStatus() {\n    return {\n      backlog: this.filteredTasks.filter((t) => t.status === 'backlog'),\n      todo: this.filteredTasks.filter((t) => t.status === 'todo'),\n      inProgress: this.filteredTasks.filter((t) => t.status === 'in-progress'),\n      review: this.filteredTasks.filter((t) => t.status === 'review'),\n      done: this.filteredTasks.filter((t) => t.status === 'done'),\n    };\n  }\n\n  get filteredPosts() {\n    if (!this.searchQuery) return this.posts;\n    const q = this.searchQuery.toLowerCase();\n    return this.posts.filter((p) => p.title.toLowerCase().includes(q));\n  }\n\n  get postsWithAuthors() {\n    return this.filteredPosts.map((post) => ({\n      post,\n      author: this.users.find((u) => u.id === post.authorId),\n    }));\n  }\n\n  get paginatedItems() {\n    const items = this.activeTab === 'users' ? this.filteredUsers : this.filteredTasks;\n    const start = (this.currentPage - 1) * this.pageSize;\n    return items.slice(start, start + this.pageSize);\n  }\n\n  get totalPages() {\n    const items = this.activeTab === 'users' ? this.filteredUsers : this.filteredTasks;\n    return Math.ceil(items.length / this.pageSize);\n  }\n\n  get hasNextPage() { return this.currentPage < this.totalPages; }\n  get hasPreviousPage() { return this.currentPage > 1; }\n\n  get globalStats() {\n    return {\n      totalProjects: this.args.projects?.length || 0,\n      totalTasks: this.tasks.length,\n      completedTasks: this.tasks.filter((t) => t.status === 'done').length,\n      overdueTasks: this.tasks.filter(\n        (t) => t.dueDate && new Date(t.dueDate) < new Date() && t.status !== 'done',\n      ).length,\n      teamSize: this.users.length,\n      progress:\n        this.tasks.length > 0\n          ? Math.round(\n              (this.tasks.filter((t) => t.status === 'done').length / this.tasks.length) * 100,\n            )\n          : 0,\n    };\n  }\n\n  get selectedTaskComments() {\n    if (!this.selectedTask) return [];\n    return this.comments.filter((c) => c.taskId === this.selectedTask.id);\n  }\n\n  get canEditPost() {\n    return this.session.currentUser?.role === 'admin' || this.session.currentUser?.role === 'editor';\n  }\n\n  @action selectUser(user) { this.selectedUser = user; }\n  @action clearSelection() { this.selectedUser = null; }\n  @action selectTask(task) { this.selectedTask = task; }\n  @action clearTaskSelection() { this.selectedTask = null; }\n  @action setTab(tab) { this.activeTab = tab; this.currentPage = 1; this.selectedUser = null; this.selectedTask = null; }\n  @action updateSearch(event) { this.searchQuery = event.target.value; this.currentPage = 1; }\n  @action toggleFilters() { this.showFilters = !this.showFilters; }\n  @action setFilterStatus(event) { this.filterStatus = event.target.value || null; this.currentPage = 1; }\n  @action setFilterPriority(event) { this.filterPriority = event.target.value || null; this.currentPage = 1; }\n  @action setFilterAssignee(event) { this.filterAssignee = event.target.value || null; this.currentPage = 1; }\n  @action clearFilters() { this.filterStatus = null; this.filterPriority = null; this.filterAssignee = null; this.currentPage = 1; }\n  @action nextPage() { if (this.hasNextPage) this.currentPage++; }\n  @action previousPage() { if (this.hasPreviousPage) this.currentPage--; }\n  @action editUser(user) { this.notify.info(`Editing ${user.name}`); }\n  @action toggleUserStatus(user) { user.active = !user.active; }\n  @action editTask(task) { this.notify.info(`Editing task #${task.id}`); }\n  @action deleteTask(task) { this.tasks = this.tasks.filter((t) => t !== task); }\n  @action changeTaskStatus(task, event) { task.status = event.target.value; }\n  @action viewPost(post) { this.router.transitionTo('posts.show', post.id); }\n  @action editPost(post) { this.router.transitionTo('posts.edit', post.id); }\n  @action replyToComment(comment) { this.newCommentBody = `@${comment.author.name} `; }\n  @action editComment(comment) { this.notify.info('Editing comment'); }\n  @action deleteComment(comment) { this.comments = this.comments.filter((c) => c !== comment); }\n  @action updateCommentInput(event) { this.newCommentBody = event.target.value; }\n  @action submitComment(event) {\n    event.preventDefault();\n    if (!this.newCommentBody.trim() || !this.selectedTask) return;\n    this.comments = [...this.comments, { id: Date.now(), taskId: this.selectedTask.id, body: this.newCommentBody, author: this.session.currentUser, createdAt: new Date(), isAuthor: true, replies: [] }];\n    this.newCommentBody = '';\n  }\n  @action setViewMode(mode) { this.viewMode = mode; }\n\n  <template>\n    <main class=\"dashboard\">\n      <header class=\"dashboard__header\">\n        <h1>Dashboard</h1>\n        <div class=\"dashboard__toolbar\">\n          <input\n            type=\"search\"\n            placeholder=\"Search…\"\n            value={{this.searchQuery}}\n            {{on \"input\" this.updateSearch}}\n          />\n          <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" this.toggleFilters}}>\n            Filters {{if this.showFilters \"▲\" \"▼\"}}\n          </button>\n          <div class=\"dashboard__view-toggle\">\n            <button type=\"button\" class={{if (eq this.viewMode \"list\") \"active\"}} {{on \"click\" (fn this.setViewMode \"list\")}}>List</button>\n            <button type=\"button\" class={{if (eq this.viewMode \"board\") \"active\"}} {{on \"click\" (fn this.setViewMode \"board\")}}>Board</button>\n          </div>\n        </div>\n      </header>\n\n      <FilterPanel\n        @visible={{this.showFilters}}\n        @members={{this.users}}\n        @onStatusChange={{this.setFilterStatus}}\n        @onPriorityChange={{this.setFilterPriority}}\n        @onAssigneeChange={{this.setFilterAssignee}}\n        @onClear={{this.clearFilters}}\n      />\n\n      <nav class=\"dashboard__tabs\">\n        <button type=\"button\" class={{if (eq this.activeTab \"overview\") \"active\"}} {{on \"click\" (fn this.setTab \"overview\")}}>\n          Overview\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"tasks\") \"active\"}} {{on \"click\" (fn this.setTab \"tasks\")}}>\n          Tasks ({{this.filteredTasks.length}})\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"users\") \"active\"}} {{on \"click\" (fn this.setTab \"users\")}}>\n          Users ({{this.filteredUsers.length}})\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"posts\") \"active\"}} {{on \"click\" (fn this.setTab \"posts\")}}>\n          Posts ({{this.postsWithAuthors.length}})\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"activity\") \"active\"}} {{on \"click\" (fn this.setTab \"activity\")}}>\n          Activity\n        </button>\n      </nav>\n\n      <section class=\"dashboard__content\">\n        {{#if this.isLoading}}\n          <LoadingSpinner @size=\"lg\" />\n        {{else if (eq this.activeTab \"overview\")}}\n          <StatsGrid @stats={{this.globalStats}} />\n          <div class=\"dashboard__overview-columns\">\n            <div class=\"dashboard__overview-main\">\n              <h2>Upcoming Deadlines</h2>\n              {{#each this.filteredTasks as |task|}}\n                {{#if task.dueDate}}\n                  <TaskCard\n                    @task={{task}}\n                    @assignee={{task.assignee}}\n                    @onStatusChange={{this.changeTaskStatus}}\n                    @onEdit={{this.editTask}}\n                    @onDelete={{this.deleteTask}}\n                  />\n                {{/if}}\n              {{/each}}\n            </div>\n            <div class=\"dashboard__overview-sidebar\">\n              <ActivityFeed @entries={{this.activityEntries}} />\n            </div>\n          </div>\n\n        {{else if (eq this.activeTab \"tasks\")}}\n          {{#if (eq this.viewMode \"board\")}}\n            <div class=\"kanban\">\n              <KanbanColumn @title=\"Backlog\" @tasks={{this.tasksByStatus.backlog}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"To Do\" @tasks={{this.tasksByStatus.todo}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"In Progress\" @tasks={{this.tasksByStatus.inProgress}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"Review\" @tasks={{this.tasksByStatus.review}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"Done\" @tasks={{this.tasksByStatus.done}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n            </div>\n          {{else}}\n            <div class=\"task-list\">\n              {{#each this.paginatedItems as |task|}}\n                <TaskCard\n                  @task={{task}}\n                  @assignee={{task.assignee}}\n                  @onStatusChange={{this.changeTaskStatus}}\n                  @onEdit={{this.editTask}}\n                  @onDelete={{this.deleteTask}}\n                />\n              {{/each}}\n              {{#unless this.filteredTasks.length}}\n                <EmptyState @icon=\"📋\" @title=\"No tasks found\" @description=\"Try adjusting your filters or create a new task.\" />\n              {{/unless}}\n            </div>\n            <Pagination @current={{this.currentPage}} @total={{this.totalPages}} @hasNext={{this.hasNextPage}} @hasPrevious={{this.hasPreviousPage}} @onNext={{this.nextPage}} @onPrevious={{this.previousPage}} />\n          {{/if}}\n\n          {{#if this.selectedTask}}\n            <aside class=\"task-detail\">\n              <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" this.clearTaskSelection}}>← Back</button>\n              <TaskCard @task={{this.selectedTask}} @assignee={{this.selectedTask.assignee}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <CommentThread\n                @comments={{this.selectedTaskComments}}\n                @newCommentBody={{this.newCommentBody}}\n                @onCommentInput={{this.updateCommentInput}}\n                @onSubmitComment={{this.submitComment}}\n                @onReply={{this.replyToComment}}\n                @onEdit={{this.editComment}}\n                @onDelete={{this.deleteComment}}\n              />\n            </aside>\n          {{/if}}\n\n        {{else if (eq this.activeTab \"users\")}}\n          {{#if this.selectedUser}}\n            <div class=\"detail-panel\">\n              <button type=\"button\" {{on \"click\" this.clearSelection}}>← Back</button>\n              <UserCard @user={{this.selectedUser}} @onSelect={{this.selectUser}} @onEdit={{this.editUser}} @onToggleStatus={{this.toggleUserStatus}} />\n            </div>\n          {{else}}\n            <div class=\"user-grid\">\n              {{#each this.paginatedItems as |user|}}\n                <UserCard @user={{user}} @onSelect={{this.selectUser}} @onEdit={{this.editUser}} @onToggleStatus={{this.toggleUserStatus}} />\n              {{/each}}\n            </div>\n            <Pagination @current={{this.currentPage}} @total={{this.totalPages}} @hasNext={{this.hasNextPage}} @hasPrevious={{this.hasPreviousPage}} @onNext={{this.nextPage}} @onPrevious={{this.previousPage}} />\n          {{/if}}\n\n        {{else if (eq this.activeTab \"posts\")}}\n          <div class=\"post-list\">\n            {{#each this.postsWithAuthors as |entry|}}\n              <PostSummary @post={{entry.post}} @author={{entry.author}} @canEdit={{this.canEditPost}} @onView={{this.viewPost}} @onEdit={{this.editPost}} />\n            {{/each}}\n            {{#unless this.postsWithAuthors.length}}\n              <EmptyState @icon=\"📝\" @title=\"No posts found\" @description=\"Try adjusting your search or create a new post.\" />\n            {{/unless}}\n          </div>\n\n        {{else if (eq this.activeTab \"activity\")}}\n          <ActivityFeed @entries={{this.activityEntries}} />\n        {{/if}}\n      </section>\n    </main>\n  </template>\n}\n"
  },
  {
    "path": "tests/bench/large.gts",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { on } from '@ember/modifier';\nimport { fn } from '@ember/helper';\nimport { inject as service } from '@ember/service';\nimport type { TOC } from '@ember/component/template-only';\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\ninterface User {\n  id: number;\n  name: string;\n  email: string;\n  role: 'admin' | 'editor' | 'viewer';\n  active: boolean;\n  department?: string;\n}\n\ninterface Task {\n  id: number;\n  title: string;\n  description?: string;\n  status: 'backlog' | 'todo' | 'in-progress' | 'review' | 'done';\n  priority: 'critical' | 'high' | 'medium' | 'low';\n  assigneeId?: number;\n  assignee?: User;\n  dueDate?: string;\n  labels?: string[];\n  estimate?: number;\n  commentCount?: number;\n  overdue?: boolean;\n}\n\ninterface Post {\n  id: number;\n  title: string;\n  excerpt: string;\n  body: string;\n  authorId: number;\n  publishedAt: Date | null;\n  tags: string[];\n  status: 'draft' | 'published' | 'archived';\n  featured?: boolean;\n  views: number;\n  likes: number;\n  commentCount: number;\n  readTime: number;\n}\n\ninterface CommentData {\n  id: number;\n  taskId: number;\n  body: string;\n  author: User;\n  createdAt: Date;\n  isAuthor: boolean;\n  replies: CommentData[];\n}\n\ninterface ActivityEntry {\n  user: User;\n  description: string;\n  timestamp: Date;\n}\n\ninterface GlobalStats {\n  totalProjects: number;\n  totalTasks: number;\n  completedTasks: number;\n  overdueTasks: number;\n  teamSize: number;\n  progress: number;\n}\n\n// ── Presentational Components ─────────────────────────────────────────────\n\nconst Avatar: TOC<{ Args: { user: User; size?: 'xs' | 'sm' | 'md' | 'lg' } }> = <template>\n  <span class=\"avatar avatar--{{if @size @size 'md'}}\" aria-label={{@user.name}}>\n    {{get @user.name 0}}\n  </span>\n</template>;\n\nconst Badge: TOC<{ Args: { variant: string }; Blocks: { default: [] } }> = <template>\n  <span class=\"badge badge--{{@variant}}\">{{yield}}</span>\n</template>;\n\nconst StatusDot: TOC<{ Args: { status: string } }> = <template>\n  <span class=\"status-dot status-dot--{{@status}}\" title={{@status}}></span>\n</template>;\n\nconst PriorityIcon: TOC<{ Args: { level: Task['priority'] } }> = <template>\n  <span class=\"priority-icon priority-icon--{{@level}}\">\n    {{#if (eq @level \"critical\")}}⬆⬆{{/if}}\n    {{#if (eq @level \"high\")}}⬆{{/if}}\n    {{#if (eq @level \"medium\")}}➡{{/if}}\n    {{#if (eq @level \"low\")}}⬇{{/if}}\n  </span>\n</template>;\n\nconst ProgressBar: TOC<{ Args: { value: number; color?: string; showLabel?: boolean } }> = <template>\n  <div class=\"progress\" role=\"progressbar\" aria-valuenow={{@value}} aria-valuemin=\"0\" aria-valuemax=\"100\">\n    <div class=\"progress__bar progress__bar--{{@color}}\" style=\"width: {{@value}}%\"></div>\n    {{#if @showLabel}}\n      <span class=\"progress__label\">{{@value}}%</span>\n    {{/if}}\n  </div>\n</template>;\n\nconst Tooltip: TOC<{ Args: { text: string }; Blocks: { default: [] } }> = <template>\n  <span class=\"tooltip\" data-tooltip={{@text}}>{{yield}}</span>\n</template>;\n\nconst EmptyState: TOC<{ Args: { icon: string; title: string; description: string; actionLabel?: string; onAction?: () => void } }> = <template>\n  <div class=\"empty-state\">\n    <div class=\"empty-state__icon\">{{@icon}}</div>\n    <h3>{{@title}}</h3>\n    <p>{{@description}}</p>\n    {{#if @actionLabel}}\n      <button type=\"button\" class=\"btn btn--primary\" {{on \"click\" @onAction}}>\n        {{@actionLabel}}\n      </button>\n    {{/if}}\n  </div>\n</template>;\n\nconst LoadingSpinner: TOC<{ Args: { size?: string } }> = <template>\n  <div class=\"spinner {{if @size (concat 'spinner--' @size)}}\" role=\"status\">\n    <span class=\"sr-only\">Loading…</span>\n  </div>\n</template>;\n\nconst Pagination: TOC<{ Args: { current: number; total: number; hasNext: boolean; hasPrevious: boolean; onNext: () => void; onPrevious: () => void } }> = <template>\n  <nav class=\"pagination\" aria-label=\"Pagination\">\n    <button\n      type=\"button\"\n      class=\"pagination__btn\"\n      disabled={{not @hasPrevious}}\n      {{on \"click\" @onPrevious}}\n    >\n      ← Previous\n    </button>\n    <span class=\"pagination__info\">\n      Page {{@current}} of {{@total}}\n    </span>\n    <button\n      type=\"button\"\n      class=\"pagination__btn\"\n      disabled={{not @hasNext}}\n      {{on \"click\" @onNext}}\n    >\n      Next →\n    </button>\n  </nav>\n</template>;\n\n// ── Card Components ───────────────────────────────────────────────────────\n\nconst UserCard: TOC<{ Args: { user: User; onSelect: (u: User) => void; onEdit?: (u: User) => void; onToggleStatus?: (u: User) => void } }> = <template>\n  <article class=\"user-card {{if @user.active '' 'user-card--inactive'}}\">\n    <div class=\"user-card__header\">\n      <Avatar @user={{@user}} @size=\"lg\" />\n      <StatusDot @status={{if @user.active \"online\" \"offline\"}} />\n    </div>\n    <div class=\"user-card__body\">\n      <strong class=\"user-card__name\">{{@user.name}}</strong>\n      <span class=\"user-card__email\">{{@user.email}}</span>\n      <Badge @variant={{@user.role}}>{{@user.role}}</Badge>\n      {{#if @user.department}}\n        <span class=\"user-card__dept\">{{@user.department}}</span>\n      {{/if}}\n    </div>\n    <div class=\"user-card__footer\">\n      <Tooltip @text=\"View profile\">\n        <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" (fn @onSelect @user)}}>\n          View\n        </button>\n      </Tooltip>\n      {{#if @onEdit}}\n        <button type=\"button\" class=\"btn btn--sm btn--outline\" {{on \"click\" (fn @onEdit @user)}}>\n          Edit\n        </button>\n      {{/if}}\n      {{#if @onToggleStatus}}\n        <button\n          type=\"button\"\n          class=\"btn btn--sm {{if @user.active 'btn--warning' 'btn--success'}}\"\n          {{on \"click\" (fn @onToggleStatus @user)}}\n        >\n          {{if @user.active \"Deactivate\" \"Activate\"}}\n        </button>\n      {{/if}}\n    </div>\n  </article>\n</template>;\n\nconst TaskCard: TOC<{ Args: { task: Task; assignee?: User; isDragging?: boolean; onStatusChange: (task: Task, e: Event) => void; onEdit: (task: Task) => void; onDelete: (task: Task) => void } }> = <template>\n  <div class=\"task-card task-card--{{@task.status}} {{if @isDragging 'task-card--dragging'}}\">\n    <div class=\"task-card__header\">\n      <PriorityIcon @level={{@task.priority}} />\n      <span class=\"task-card__id\">#{{@task.id}}</span>\n      {{#if @task.labels}}\n        <div class=\"task-card__labels\">\n          {{#each @task.labels as |label|}}\n            <Badge @variant=\"label\">{{label}}</Badge>\n          {{/each}}\n        </div>\n      {{/if}}\n    </div>\n    <h3 class=\"task-card__title\">{{@task.title}}</h3>\n    {{#if @task.description}}\n      <p class=\"task-card__desc\">{{@task.description}}</p>\n    {{/if}}\n    <div class=\"task-card__meta\">\n      {{#if @assignee}}\n        <Tooltip @text={{@assignee.name}}>\n          <Avatar @user={{@assignee}} @size=\"sm\" />\n        </Tooltip>\n      {{/if}}\n      {{#if @task.dueDate}}\n        <span class=\"task-card__due {{if @task.overdue 'task-card__due--late'}}\">\n          {{@task.dueDate}}\n        </span>\n      {{/if}}\n      {{#if @task.estimate}}\n        <span class=\"task-card__estimate\">{{@task.estimate}}h</span>\n      {{/if}}\n      {{#if @task.commentCount}}\n        <span class=\"task-card__comments\">💬 {{@task.commentCount}}</span>\n      {{/if}}\n    </div>\n    <div class=\"task-card__actions\">\n      <select {{on \"change\" (fn @onStatusChange @task)}}>\n        <option value=\"backlog\" selected={{eq @task.status \"backlog\"}}>Backlog</option>\n        <option value=\"todo\" selected={{eq @task.status \"todo\"}}>To Do</option>\n        <option value=\"in-progress\" selected={{eq @task.status \"in-progress\"}}>In Progress</option>\n        <option value=\"review\" selected={{eq @task.status \"review\"}}>Review</option>\n        <option value=\"done\" selected={{eq @task.status \"done\"}}>Done</option>\n      </select>\n      <button type=\"button\" class=\"btn btn--icon\" {{on \"click\" (fn @onEdit @task)}}>Edit</button>\n      <button type=\"button\" class=\"btn btn--icon btn--danger\" {{on \"click\" (fn @onDelete @task)}}>Delete</button>\n    </div>\n  </div>\n</template>;\n\nconst PostSummary: TOC<{ Args: { post: Post; author: User; canEdit: boolean; onView: (post: Post) => void; onEdit: (post: Post) => void } }> = <template>\n  <article class=\"post-summary\">\n    <div class=\"post-summary__header\">\n      <h2 class=\"post-summary__title\">{{@post.title}}</h2>\n      {{#if @post.featured}}\n        <Badge @variant=\"featured\">Featured</Badge>\n      {{/if}}\n      <Badge @variant={{@post.status}}>{{@post.status}}</Badge>\n    </div>\n    <p class=\"post-summary__meta\">\n      By <Avatar @user={{@author}} @size=\"sm\" /> {{@author.name}}\n      {{#if @post.publishedAt}}\n        · Published {{@post.publishedAt}}\n      {{else}}\n        · <em>Draft</em>\n      {{/if}}\n      · {{@post.readTime}} min read\n    </p>\n    <p class=\"post-summary__body\">{{@post.excerpt}}</p>\n    {{#if @post.tags.length}}\n      <ul class=\"post-summary__tags\">\n        {{#each @post.tags as |tag|}}\n          <li>\n            <Badge @variant=\"tag\">{{tag}}</Badge>\n          </li>\n        {{/each}}\n      </ul>\n    {{/if}}\n    <div class=\"post-summary__stats\">\n      <span>👁 {{@post.views}}</span>\n      <span>❤ {{@post.likes}}</span>\n      <span>💬 {{@post.commentCount}}</span>\n    </div>\n    <div class=\"post-summary__actions\">\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" (fn @onView @post)}}>\n        Read More\n      </button>\n      {{#if @canEdit}}\n        <button type=\"button\" class=\"btn btn--sm btn--outline\" {{on \"click\" (fn @onEdit @post)}}>\n          Edit\n        </button>\n      {{/if}}\n    </div>\n  </article>\n</template>;\n\nconst CommentThread: TOC<{ Args: { comments: CommentData[]; newCommentBody: string; onCommentInput: (e: Event) => void; onSubmitComment: (e: Event) => void; onReply: (c: CommentData) => void; onEdit: (c: CommentData) => void; onDelete: (c: CommentData) => void } }> = <template>\n  <div class=\"comment-thread\">\n    {{#each @comments as |comment|}}\n      <div class=\"comment {{if comment.isAuthor 'comment--own'}}\">\n        <Avatar @user={{comment.author}} @size=\"sm\" />\n        <div class=\"comment__body\">\n          <div class=\"comment__header\">\n            <strong>{{comment.author.name}}</strong>\n            <time>{{comment.createdAt}}</time>\n          </div>\n          <p>{{comment.body}}</p>\n          <div class=\"comment__actions\">\n            <button type=\"button\" class=\"btn btn--link\" {{on \"click\" (fn @onReply comment)}}>\n              Reply\n            </button>\n            {{#if comment.isAuthor}}\n              <button type=\"button\" class=\"btn btn--link\" {{on \"click\" (fn @onEdit comment)}}>\n                Edit\n              </button>\n              <button type=\"button\" class=\"btn btn--link btn--danger\" {{on \"click\" (fn @onDelete comment)}}>\n                Delete\n              </button>\n            {{/if}}\n          </div>\n          {{#if comment.replies.length}}\n            <div class=\"comment__replies\">\n              {{#each comment.replies as |reply|}}\n                <div class=\"comment comment--reply\">\n                  <Avatar @user={{reply.author}} @size=\"xs\" />\n                  <div class=\"comment__body\">\n                    <strong>{{reply.author.name}}</strong>\n                    <time>{{reply.createdAt}}</time>\n                    <p>{{reply.body}}</p>\n                  </div>\n                </div>\n              {{/each}}\n            </div>\n          {{/if}}\n        </div>\n      </div>\n    {{/each}}\n    <form class=\"comment-form\" {{on \"submit\" @onSubmitComment}}>\n      <textarea\n        placeholder=\"Write a comment…\"\n        value={{@newCommentBody}}\n        {{on \"input\" @onCommentInput}}\n      ></textarea>\n      <button type=\"submit\" class=\"btn btn--primary btn--sm\" disabled={{not @newCommentBody}}>\n        Post Comment\n      </button>\n    </form>\n  </div>\n</template>;\n\nconst ActivityFeed: TOC<{ Args: { entries: ActivityEntry[] } }> = <template>\n  <div class=\"activity-feed\">\n    <h3 class=\"activity-feed__title\">Recent Activity</h3>\n    {{#each @entries as |entry|}}\n      <div class=\"activity-feed__item\">\n        <Avatar @user={{entry.user}} @size=\"xs\" />\n        <div class=\"activity-feed__content\">\n          <span class=\"activity-feed__action\">\n            <strong>{{entry.user.name}}</strong>\n            {{entry.description}}\n          </span>\n          <time class=\"activity-feed__time\">{{entry.timestamp}}</time>\n        </div>\n      </div>\n    {{/each}}\n    {{#unless @entries.length}}\n      <p class=\"activity-feed__empty\">No recent activity</p>\n    {{/unless}}\n  </div>\n</template>;\n\nconst KanbanColumn: TOC<{ Args: { title: string; tasks: Task[]; isDropTarget?: boolean; onDragOver?: (e: Event) => void; onDrop?: (e: Event) => void; onStatusChange: (task: Task, e: Event) => void; onEdit: (task: Task) => void; onDelete: (task: Task) => void } }> = <template>\n  <div\n    class=\"kanban-column {{if @isDropTarget 'kanban-column--drop-target'}}\"\n    {{on \"dragover\" @onDragOver}}\n    {{on \"drop\" @onDrop}}\n  >\n    <div class=\"kanban-column__header\">\n      <h3>{{@title}}</h3>\n      <Badge @variant=\"count\">{{@tasks.length}}</Badge>\n    </div>\n    <div class=\"kanban-column__body\">\n      {{#each @tasks as |task|}}\n        <TaskCard\n          @task={{task}}\n          @assignee={{task.assignee}}\n          @onStatusChange={{@onStatusChange}}\n          @onEdit={{@onEdit}}\n          @onDelete={{@onDelete}}\n        />\n      {{/each}}\n      {{#unless @tasks.length}}\n        <p class=\"kanban-column__empty\">No tasks</p>\n      {{/unless}}\n    </div>\n  </div>\n</template>;\n\nconst StatsGrid: TOC<{ Args: { stats: GlobalStats } }> = <template>\n  <div class=\"stats-grid\">\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.totalProjects}}</span>\n      <span class=\"stats-grid__label\">Projects</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.totalTasks}}</span>\n      <span class=\"stats-grid__label\">Tasks</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.completedTasks}}</span>\n      <span class=\"stats-grid__label\">Completed</span>\n    </div>\n    <div class=\"stats-grid__card stats-grid__card--warning\">\n      <span class=\"stats-grid__value\">{{@stats.overdueTasks}}</span>\n      <span class=\"stats-grid__label\">Overdue</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <span class=\"stats-grid__value\">{{@stats.teamSize}}</span>\n      <span class=\"stats-grid__label\">Team Members</span>\n    </div>\n    <div class=\"stats-grid__card\">\n      <ProgressBar @value={{@stats.progress}} @color=\"success\" @showLabel={{true}} />\n      <span class=\"stats-grid__label\">Overall Progress</span>\n    </div>\n  </div>\n</template>;\n\nconst FilterPanel: TOC<{ Args: { visible: boolean; members: User[]; onStatusChange: (e: Event) => void; onPriorityChange: (e: Event) => void; onAssigneeChange: (e: Event) => void; onClear: () => void } }> = <template>\n  {{#if @visible}}\n    <div class=\"filter-panel\">\n      <div class=\"filter-panel__group\">\n        <label>Status</label>\n        <select {{on \"change\" @onStatusChange}}>\n          <option value=\"\">All Statuses</option>\n          <option value=\"backlog\">Backlog</option>\n          <option value=\"todo\">To Do</option>\n          <option value=\"in-progress\">In Progress</option>\n          <option value=\"review\">Review</option>\n          <option value=\"done\">Done</option>\n        </select>\n      </div>\n      <div class=\"filter-panel__group\">\n        <label>Priority</label>\n        <select {{on \"change\" @onPriorityChange}}>\n          <option value=\"\">All Priorities</option>\n          <option value=\"critical\">Critical</option>\n          <option value=\"high\">High</option>\n          <option value=\"medium\">Medium</option>\n          <option value=\"low\">Low</option>\n        </select>\n      </div>\n      <div class=\"filter-panel__group\">\n        <label>Assignee</label>\n        <select {{on \"change\" @onAssigneeChange}}>\n          <option value=\"\">All Members</option>\n          {{#each @members as |member|}}\n            <option value={{member.id}}>{{member.name}}</option>\n          {{/each}}\n        </select>\n      </div>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onClear}}>\n        Clear All Filters\n      </button>\n    </div>\n  {{/if}}\n</template>;\n\n// ── Main Component ────────────────────────────────────────────────────────\n\ninterface DashboardArgs {\n  projects?: { length: number }[];\n}\n\nexport default class Dashboard extends Component<{ Args: DashboardArgs }> {\n  @service declare store: any;\n  @service declare router: any;\n  @service('notifications') declare notify: any;\n  @service declare session: any;\n\n  @tracked users: User[] = [];\n  @tracked tasks: Task[] = [];\n  @tracked posts: Post[] = [];\n  @tracked comments: CommentData[] = [];\n  @tracked activityEntries: ActivityEntry[] = [];\n  @tracked selectedUser: User | null = null;\n  @tracked selectedTask: Task | null = null;\n  @tracked searchQuery = '';\n  @tracked activeTab = 'overview';\n  @tracked isLoading = false;\n  @tracked currentPage = 1;\n  @tracked pageSize = 20;\n  @tracked showFilters = false;\n  @tracked filterStatus: string | null = null;\n  @tracked filterPriority: string | null = null;\n  @tracked filterAssignee: string | null = null;\n  @tracked newCommentBody = '';\n  @tracked viewMode: 'list' | 'board' = 'list';\n\n  get filteredUsers(): User[] {\n    if (!this.searchQuery) return this.users;\n    const q = this.searchQuery.toLowerCase();\n    return this.users.filter(\n      (u) => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q),\n    );\n  }\n\n  get filteredTasks(): Task[] {\n    let result = this.tasks;\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter((t) => t.title.toLowerCase().includes(q));\n    }\n    if (this.filterStatus) result = result.filter((t) => t.status === this.filterStatus);\n    if (this.filterPriority) result = result.filter((t) => t.priority === this.filterPriority);\n    if (this.filterAssignee) result = result.filter((t) => t.assigneeId === Number(this.filterAssignee));\n    return result;\n  }\n\n  get tasksByStatus(): Record<string, Task[]> {\n    return {\n      backlog: this.filteredTasks.filter((t) => t.status === 'backlog'),\n      todo: this.filteredTasks.filter((t) => t.status === 'todo'),\n      inProgress: this.filteredTasks.filter((t) => t.status === 'in-progress'),\n      review: this.filteredTasks.filter((t) => t.status === 'review'),\n      done: this.filteredTasks.filter((t) => t.status === 'done'),\n    };\n  }\n\n  get filteredPosts(): Post[] {\n    if (!this.searchQuery) return this.posts;\n    const q = this.searchQuery.toLowerCase();\n    return this.posts.filter((p) => p.title.toLowerCase().includes(q));\n  }\n\n  get postsWithAuthors(): Array<{ post: Post; author: User | undefined }> {\n    return this.filteredPosts.map((post) => ({\n      post,\n      author: this.users.find((u) => u.id === post.authorId),\n    }));\n  }\n\n  get paginatedItems(): Array<User | Task> {\n    const items = this.activeTab === 'users' ? this.filteredUsers : this.filteredTasks;\n    const start = (this.currentPage - 1) * this.pageSize;\n    return items.slice(start, start + this.pageSize);\n  }\n\n  get totalPages(): number {\n    const items = this.activeTab === 'users' ? this.filteredUsers : this.filteredTasks;\n    return Math.ceil(items.length / this.pageSize);\n  }\n\n  get hasNextPage(): boolean { return this.currentPage < this.totalPages; }\n  get hasPreviousPage(): boolean { return this.currentPage > 1; }\n\n  get globalStats(): GlobalStats {\n    return {\n      totalProjects: this.args.projects?.length || 0,\n      totalTasks: this.tasks.length,\n      completedTasks: this.tasks.filter((t) => t.status === 'done').length,\n      overdueTasks: this.tasks.filter(\n        (t) => t.dueDate && new Date(t.dueDate) < new Date() && t.status !== 'done',\n      ).length,\n      teamSize: this.users.length,\n      progress:\n        this.tasks.length > 0\n          ? Math.round(\n              (this.tasks.filter((t) => t.status === 'done').length / this.tasks.length) * 100,\n            )\n          : 0,\n    };\n  }\n\n  get selectedTaskComments(): CommentData[] {\n    if (!this.selectedTask) return [];\n    return this.comments.filter((c) => c.taskId === this.selectedTask!.id);\n  }\n\n  get canEditPost(): boolean {\n    return this.session.currentUser?.role === 'admin' || this.session.currentUser?.role === 'editor';\n  }\n\n  @action selectUser(user: User) { this.selectedUser = user; }\n  @action clearSelection() { this.selectedUser = null; }\n  @action selectTask(task: Task) { this.selectedTask = task; }\n  @action clearTaskSelection() { this.selectedTask = null; }\n  @action setTab(tab: string) { this.activeTab = tab; this.currentPage = 1; this.selectedUser = null; this.selectedTask = null; }\n  @action updateSearch(event: Event) { this.searchQuery = (event.target as HTMLInputElement).value; this.currentPage = 1; }\n  @action toggleFilters() { this.showFilters = !this.showFilters; }\n  @action setFilterStatus(event: Event) { this.filterStatus = (event.target as HTMLSelectElement).value || null; this.currentPage = 1; }\n  @action setFilterPriority(event: Event) { this.filterPriority = (event.target as HTMLSelectElement).value || null; this.currentPage = 1; }\n  @action setFilterAssignee(event: Event) { this.filterAssignee = (event.target as HTMLSelectElement).value || null; this.currentPage = 1; }\n  @action clearFilters() { this.filterStatus = null; this.filterPriority = null; this.filterAssignee = null; this.currentPage = 1; }\n  @action nextPage() { if (this.hasNextPage) this.currentPage++; }\n  @action previousPage() { if (this.hasPreviousPage) this.currentPage--; }\n  @action editUser(user: User) { this.notify.info(`Editing ${user.name}`); }\n  @action toggleUserStatus(user: User) { user.active = !user.active; }\n  @action editTask(task: Task) { this.notify.info(`Editing task #${task.id}`); }\n  @action deleteTask(task: Task) { this.tasks = this.tasks.filter((t) => t !== task); }\n  @action changeTaskStatus(task: Task, event: Event) { task.status = (event.target as HTMLSelectElement).value as Task['status']; }\n  @action viewPost(post: Post) { this.router.transitionTo('posts.show', post.id); }\n  @action editPost(post: Post) { this.router.transitionTo('posts.edit', post.id); }\n  @action replyToComment(comment: CommentData) { this.newCommentBody = `@${comment.author.name} `; }\n  @action editComment(comment: CommentData) { this.notify.info('Editing comment'); }\n  @action deleteComment(comment: CommentData) { this.comments = this.comments.filter((c) => c !== comment); }\n  @action updateCommentInput(event: Event) { this.newCommentBody = (event.target as HTMLTextAreaElement).value; }\n  @action submitComment(event: Event) {\n    event.preventDefault();\n    if (!this.newCommentBody.trim() || !this.selectedTask) return;\n    this.comments = [...this.comments, { id: Date.now(), taskId: this.selectedTask.id, body: this.newCommentBody, author: this.session.currentUser, createdAt: new Date(), isAuthor: true, replies: [] }];\n    this.newCommentBody = '';\n  }\n  @action setViewMode(mode: 'list' | 'board') { this.viewMode = mode; }\n\n  <template>\n    <main class=\"dashboard\">\n      <header class=\"dashboard__header\">\n        <h1>Dashboard</h1>\n        <div class=\"dashboard__toolbar\">\n          <input\n            type=\"search\"\n            placeholder=\"Search…\"\n            value={{this.searchQuery}}\n            {{on \"input\" this.updateSearch}}\n          />\n          <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" this.toggleFilters}}>\n            Filters {{if this.showFilters \"▲\" \"▼\"}}\n          </button>\n          <div class=\"dashboard__view-toggle\">\n            <button type=\"button\" class={{if (eq this.viewMode \"list\") \"active\"}} {{on \"click\" (fn this.setViewMode \"list\")}}>List</button>\n            <button type=\"button\" class={{if (eq this.viewMode \"board\") \"active\"}} {{on \"click\" (fn this.setViewMode \"board\")}}>Board</button>\n          </div>\n        </div>\n      </header>\n\n      <FilterPanel\n        @visible={{this.showFilters}}\n        @members={{this.users}}\n        @onStatusChange={{this.setFilterStatus}}\n        @onPriorityChange={{this.setFilterPriority}}\n        @onAssigneeChange={{this.setFilterAssignee}}\n        @onClear={{this.clearFilters}}\n      />\n\n      <nav class=\"dashboard__tabs\">\n        <button type=\"button\" class={{if (eq this.activeTab \"overview\") \"active\"}} {{on \"click\" (fn this.setTab \"overview\")}}>\n          Overview\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"tasks\") \"active\"}} {{on \"click\" (fn this.setTab \"tasks\")}}>\n          Tasks ({{this.filteredTasks.length}})\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"users\") \"active\"}} {{on \"click\" (fn this.setTab \"users\")}}>\n          Users ({{this.filteredUsers.length}})\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"posts\") \"active\"}} {{on \"click\" (fn this.setTab \"posts\")}}>\n          Posts ({{this.postsWithAuthors.length}})\n        </button>\n        <button type=\"button\" class={{if (eq this.activeTab \"activity\") \"active\"}} {{on \"click\" (fn this.setTab \"activity\")}}>\n          Activity\n        </button>\n      </nav>\n\n      <section class=\"dashboard__content\">\n        {{#if this.isLoading}}\n          <LoadingSpinner @size=\"lg\" />\n        {{else if (eq this.activeTab \"overview\")}}\n          <StatsGrid @stats={{this.globalStats}} />\n          <div class=\"dashboard__overview-columns\">\n            <div class=\"dashboard__overview-main\">\n              <h2>Upcoming Deadlines</h2>\n              {{#each this.filteredTasks as |task|}}\n                {{#if task.dueDate}}\n                  <TaskCard\n                    @task={{task}}\n                    @assignee={{task.assignee}}\n                    @onStatusChange={{this.changeTaskStatus}}\n                    @onEdit={{this.editTask}}\n                    @onDelete={{this.deleteTask}}\n                  />\n                {{/if}}\n              {{/each}}\n            </div>\n            <div class=\"dashboard__overview-sidebar\">\n              <ActivityFeed @entries={{this.activityEntries}} />\n            </div>\n          </div>\n\n        {{else if (eq this.activeTab \"tasks\")}}\n          {{#if (eq this.viewMode \"board\")}}\n            <div class=\"kanban\">\n              <KanbanColumn @title=\"Backlog\" @tasks={{this.tasksByStatus.backlog}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"To Do\" @tasks={{this.tasksByStatus.todo}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"In Progress\" @tasks={{this.tasksByStatus.inProgress}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"Review\" @tasks={{this.tasksByStatus.review}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <KanbanColumn @title=\"Done\" @tasks={{this.tasksByStatus.done}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n            </div>\n          {{else}}\n            <div class=\"task-list\">\n              {{#each this.paginatedItems as |task|}}\n                <TaskCard\n                  @task={{task}}\n                  @assignee={{task.assignee}}\n                  @onStatusChange={{this.changeTaskStatus}}\n                  @onEdit={{this.editTask}}\n                  @onDelete={{this.deleteTask}}\n                />\n              {{/each}}\n              {{#unless this.filteredTasks.length}}\n                <EmptyState @icon=\"📋\" @title=\"No tasks found\" @description=\"Try adjusting your filters or create a new task.\" />\n              {{/unless}}\n            </div>\n            <Pagination @current={{this.currentPage}} @total={{this.totalPages}} @hasNext={{this.hasNextPage}} @hasPrevious={{this.hasPreviousPage}} @onNext={{this.nextPage}} @onPrevious={{this.previousPage}} />\n          {{/if}}\n\n          {{#if this.selectedTask}}\n            <aside class=\"task-detail\">\n              <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" this.clearTaskSelection}}>← Back</button>\n              <TaskCard @task={{this.selectedTask}} @assignee={{this.selectedTask.assignee}} @onStatusChange={{this.changeTaskStatus}} @onEdit={{this.editTask}} @onDelete={{this.deleteTask}} />\n              <CommentThread\n                @comments={{this.selectedTaskComments}}\n                @newCommentBody={{this.newCommentBody}}\n                @onCommentInput={{this.updateCommentInput}}\n                @onSubmitComment={{this.submitComment}}\n                @onReply={{this.replyToComment}}\n                @onEdit={{this.editComment}}\n                @onDelete={{this.deleteComment}}\n              />\n            </aside>\n          {{/if}}\n\n        {{else if (eq this.activeTab \"users\")}}\n          {{#if this.selectedUser}}\n            <div class=\"detail-panel\">\n              <button type=\"button\" {{on \"click\" this.clearSelection}}>← Back</button>\n              <UserCard @user={{this.selectedUser}} @onSelect={{this.selectUser}} @onEdit={{this.editUser}} @onToggleStatus={{this.toggleUserStatus}} />\n            </div>\n          {{else}}\n            <div class=\"user-grid\">\n              {{#each this.paginatedItems as |user|}}\n                <UserCard @user={{user}} @onSelect={{this.selectUser}} @onEdit={{this.editUser}} @onToggleStatus={{this.toggleUserStatus}} />\n              {{/each}}\n            </div>\n            <Pagination @current={{this.currentPage}} @total={{this.totalPages}} @hasNext={{this.hasNextPage}} @hasPrevious={{this.hasPreviousPage}} @onNext={{this.nextPage}} @onPrevious={{this.previousPage}} />\n          {{/if}}\n\n        {{else if (eq this.activeTab \"posts\")}}\n          <div class=\"post-list\">\n            {{#each this.postsWithAuthors as |entry|}}\n              <PostSummary @post={{entry.post}} @author={{entry.author}} @canEdit={{this.canEditPost}} @onView={{this.viewPost}} @onEdit={{this.editPost}} />\n            {{/each}}\n            {{#unless this.postsWithAuthors.length}}\n              <EmptyState @icon=\"📝\" @title=\"No posts found\" @description=\"Try adjusting your search or create a new post.\" />\n            {{/unless}}\n          </div>\n\n        {{else if (eq this.activeTab \"activity\")}}\n          <ActivityFeed @entries={{this.activityEntries}} />\n        {{/if}}\n      </section>\n    </main>\n  </template>\n}\n"
  },
  {
    "path": "tests/bench/large.js",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { inject as service } from '@ember/service';\nimport { later, cancel } from '@ember/runloop';\nimport { A } from '@ember/array';\n\nexport default class ProjectDashboardComponent extends Component {\n  @service store;\n  @service router;\n  @service('notifications') notify;\n  @service intl;\n  @service session;\n  @service('analytics') tracking;\n\n  @tracked projects = [];\n  @tracked tasks = [];\n  @tracked users = [];\n  @tracked comments = [];\n  @tracked selectedProject = null;\n  @tracked selectedTask = null;\n  @tracked searchQuery = '';\n  @tracked activeTab = 'overview';\n  @tracked isLoading = false;\n  @tracked sortField = 'name';\n  @tracked sortDirection = 'asc';\n  @tracked currentPage = 1;\n  @tracked pageSize = 25;\n  @tracked totalRecords = 0;\n  @tracked showFilters = false;\n  @tracked filterStatus = null;\n  @tracked filterAssignee = null;\n  @tracked filterPriority = null;\n  @tracked dateRange = null;\n  @tracked errorMessage = null;\n  @tracked showCreateModal = false;\n  @tracked showEditModal = false;\n  @tracked editingEntity = null;\n\n  @tracked newProjectName = '';\n  @tracked newProjectDescription = '';\n  @tracked newProjectColor = '#3B82F6';\n  @tracked newProjectDeadline = null;\n\n  @tracked newTaskTitle = '';\n  @tracked newTaskDescription = '';\n  @tracked newTaskPriority = 'medium';\n  @tracked newTaskAssignee = null;\n  @tracked newTaskDueDate = null;\n  @tracked newTaskLabels = [];\n  @tracked newTaskEstimate = null;\n\n  @tracked draggedTask = null;\n  @tracked dragOverColumn = null;\n\n  @tracked viewMode = 'list';\n  @tracked showCompletedTasks = true;\n  @tracked groupBy = 'status';\n  @tracked isCollapsed = {};\n\n  @tracked activityLog = [];\n  @tracked showActivitySidebar = false;\n\n  @tracked timeEntries = [];\n  @tracked activeTimer = null;\n  @tracked timerElapsed = 0;\n  _timerInterval = null;\n\n  // ── Computed: Projects ──────────────────────────────────────────────────\n\n  get filteredProjects() {\n    let result = this.projects;\n\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter(\n        (p) =>\n          p.name.toLowerCase().includes(q) ||\n          (p.description && p.description.toLowerCase().includes(q))\n      );\n    }\n\n    if (this.filterStatus) {\n      result = result.filter((p) => p.status === this.filterStatus);\n    }\n\n    return result;\n  }\n\n  get sortedProjects() {\n    const sorted = [...this.filteredProjects].sort((a, b) => {\n      const aVal = a[this.sortField];\n      const bVal = b[this.sortField];\n      if (aVal < bVal) return -1;\n      if (aVal > bVal) return 1;\n      return 0;\n    });\n    return this.sortDirection === 'desc' ? sorted.reverse() : sorted;\n  }\n\n  get paginatedProjects() {\n    const start = (this.currentPage - 1) * this.pageSize;\n    return this.sortedProjects.slice(start, start + this.pageSize);\n  }\n\n  get totalPages() {\n    return Math.ceil(this.sortedProjects.length / this.pageSize);\n  }\n\n  get hasNextPage() {\n    return this.currentPage < this.totalPages;\n  }\n\n  get hasPreviousPage() {\n    return this.currentPage > 1;\n  }\n\n  // ── Computed: Tasks ─────────────────────────────────────────────────────\n\n  get currentProjectTasks() {\n    if (!this.selectedProject) return this.tasks;\n    return this.tasks.filter((t) => t.projectId === this.selectedProject.id);\n  }\n\n  get filteredTasks() {\n    let result = this.currentProjectTasks;\n\n    if (!this.showCompletedTasks) {\n      result = result.filter((t) => t.status !== 'done');\n    }\n\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter(\n        (t) =>\n          t.title.toLowerCase().includes(q) ||\n          (t.description && t.description.toLowerCase().includes(q))\n      );\n    }\n\n    if (this.filterAssignee) {\n      result = result.filter((t) => t.assigneeId === this.filterAssignee);\n    }\n\n    if (this.filterPriority) {\n      result = result.filter((t) => t.priority === this.filterPriority);\n    }\n\n    if (this.filterStatus) {\n      result = result.filter((t) => t.status === this.filterStatus);\n    }\n\n    if (this.dateRange) {\n      result = result.filter((t) => {\n        if (!t.dueDate) return false;\n        const due = new Date(t.dueDate);\n        return due >= this.dateRange.start && due <= this.dateRange.end;\n      });\n    }\n\n    return result;\n  }\n\n  get groupedTasks() {\n    const groups = {};\n    for (const task of this.filteredTasks) {\n      const key = task[this.groupBy] || 'none';\n      if (!groups[key]) groups[key] = [];\n      groups[key].push(task);\n    }\n    return groups;\n  }\n\n  get tasksByStatus() {\n    return {\n      backlog: this.filteredTasks.filter((t) => t.status === 'backlog'),\n      todo: this.filteredTasks.filter((t) => t.status === 'todo'),\n      inProgress: this.filteredTasks.filter((t) => t.status === 'in-progress'),\n      review: this.filteredTasks.filter((t) => t.status === 'review'),\n      done: this.filteredTasks.filter((t) => t.status === 'done'),\n    };\n  }\n\n  get overdueTasks() {\n    return this.filteredTasks.filter(\n      (t) => t.dueDate && new Date(t.dueDate) < new Date() && t.status !== 'done'\n    );\n  }\n\n  get upcomingDeadlines() {\n    const nextWeek = new Date();\n    nextWeek.setDate(nextWeek.getDate() + 7);\n    return this.filteredTasks\n      .filter(\n        (t) =>\n          t.dueDate &&\n          new Date(t.dueDate) >= new Date() &&\n          new Date(t.dueDate) <= nextWeek &&\n          t.status !== 'done'\n      )\n      .sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));\n  }\n\n  // ── Computed: Users & Stats ─────────────────────────────────────────────\n\n  get projectMembers() {\n    if (!this.selectedProject) return this.users;\n    return this.users.filter((u) => this.selectedProject.memberIds.includes(u.id));\n  }\n\n  get taskAssignmentMap() {\n    const map = {};\n    for (const user of this.projectMembers) {\n      map[user.id] = this.currentProjectTasks.filter((t) => t.assigneeId === user.id);\n    }\n    return map;\n  }\n\n  get projectStats() {\n    const tasks = this.currentProjectTasks;\n    const total = tasks.length;\n    const done = tasks.filter((t) => t.status === 'done').length;\n    const inProgress = tasks.filter((t) => t.status === 'in-progress').length;\n    const overdue = this.overdueTasks.length;\n    const totalEstimate = tasks.reduce((sum, t) => sum + (t.estimate || 0), 0);\n    const completedEstimate = tasks\n      .filter((t) => t.status === 'done')\n      .reduce((sum, t) => sum + (t.estimate || 0), 0);\n\n    return {\n      total,\n      done,\n      inProgress,\n      overdue,\n      progress: total > 0 ? Math.round((done / total) * 100) : 0,\n      velocity: completedEstimate,\n      remaining: totalEstimate - completedEstimate,\n    };\n  }\n\n  get globalStats() {\n    return {\n      totalProjects: this.projects.length,\n      activeProjects: this.projects.filter((p) => p.status === 'active').length,\n      totalTasks: this.tasks.length,\n      completedTasks: this.tasks.filter((t) => t.status === 'done').length,\n      overdueTasks: this.overdueTasks.length,\n      teamSize: this.users.length,\n    };\n  }\n\n  get burndownData() {\n    const tasks = this.currentProjectTasks;\n    const total = tasks.length;\n    const byDate = {};\n\n    for (const task of tasks) {\n      if (task.completedAt) {\n        const dateKey = new Date(task.completedAt).toISOString().slice(0, 10);\n        byDate[dateKey] = (byDate[dateKey] || 0) + 1;\n      }\n    }\n\n    let remaining = total;\n    const dates = Object.keys(byDate).sort();\n    return dates.map((date) => {\n      remaining -= byDate[date];\n      return { date, remaining, completed: byDate[date] };\n    });\n  }\n\n  // ── Computed: Time Tracking ─────────────────────────────────────────────\n\n  get totalTimeSpent() {\n    return this.timeEntries.reduce((sum, entry) => sum + entry.duration, 0);\n  }\n\n  get timeByTask() {\n    const map = {};\n    for (const entry of this.timeEntries) {\n      if (!map[entry.taskId]) map[entry.taskId] = 0;\n      map[entry.taskId] += entry.duration;\n    }\n    return map;\n  }\n\n  get timeByUser() {\n    const map = {};\n    for (const entry of this.timeEntries) {\n      if (!map[entry.userId]) map[entry.userId] = 0;\n      map[entry.userId] += entry.duration;\n    }\n    return map;\n  }\n\n  get isTimerRunning() {\n    return this.activeTimer !== null;\n  }\n\n  // ── Computed: Navigation ────────────────────────────────────────────────\n\n  get isOverviewTab() {\n    return this.activeTab === 'overview';\n  }\n\n  get isTasksTab() {\n    return this.activeTab === 'tasks';\n  }\n\n  get isBoardTab() {\n    return this.activeTab === 'board';\n  }\n\n  get isTimelineTab() {\n    return this.activeTab === 'timeline';\n  }\n\n  get isMembersTab() {\n    return this.activeTab === 'members';\n  }\n\n  get isSettingsTab() {\n    return this.activeTab === 'settings';\n  }\n\n  get isActivityTab() {\n    return this.activeTab === 'activity';\n  }\n\n  // ── Actions: Data Loading ───────────────────────────────────────────────\n\n  @action\n  async loadData() {\n    this.isLoading = true;\n    this.errorMessage = null;\n\n    try {\n      const [projects, tasks, users, comments] = await Promise.all([\n        this.store.findAll('project'),\n        this.store.findAll('task'),\n        this.store.findAll('user'),\n        this.store.findAll('comment'),\n      ]);\n      this.projects = projects.slice();\n      this.tasks = tasks.slice();\n      this.users = users.slice();\n      this.comments = comments.slice();\n      this.totalRecords = projects.length + tasks.length;\n      this.tracking.track('dashboard_loaded', { projectCount: projects.length });\n    } catch (error) {\n      this.errorMessage = error.message;\n      this.notify.error('Failed to load dashboard data');\n    } finally {\n      this.isLoading = false;\n    }\n  }\n\n  @action\n  async refreshData() {\n    await this.loadData();\n    this.notify.info('Data refreshed');\n  }\n\n  // ── Actions: Project CRUD ───────────────────────────────────────────────\n\n  @action\n  selectProject(project) {\n    this.selectedProject = project;\n    this.selectedTask = null;\n    this.currentPage = 1;\n    this.tracking.track('project_selected', { projectId: project.id });\n  }\n\n  @action\n  clearProjectSelection() {\n    this.selectedProject = null;\n    this.selectedTask = null;\n  }\n\n  @action\n  async createProject() {\n    const name = this.newProjectName.trim();\n    if (!name) return;\n\n    const project = this.store.createRecord('project', {\n      name,\n      description: this.newProjectDescription.trim(),\n      color: this.newProjectColor,\n      deadline: this.newProjectDeadline,\n      status: 'active',\n      memberIds: [this.session.currentUser.id],\n      createdAt: new Date(),\n    });\n\n    try {\n      await project.save();\n      this.projects = [...this.projects, project];\n      this._resetProjectForm();\n      this.showCreateModal = false;\n      this.notify.success(`Project \"${name}\" created`);\n      this.tracking.track('project_created', { projectId: project.id });\n    } catch (error) {\n      project.rollbackAttributes();\n      this.notify.error('Failed to create project');\n    }\n  }\n\n  @action\n  async updateProject(project, changes) {\n    const original = {};\n    for (const [key, value] of Object.entries(changes)) {\n      original[key] = project[key];\n      project[key] = value;\n    }\n\n    try {\n      await project.save();\n      this._logActivity('project_updated', project);\n      this.notify.success('Project updated');\n    } catch (error) {\n      for (const [key, value] of Object.entries(original)) {\n        project[key] = value;\n      }\n      this.notify.error('Failed to update project');\n    }\n  }\n\n  @action\n  async deleteProject(project) {\n    try {\n      const projectTasks = this.tasks.filter((t) => t.projectId === project.id);\n      this.projects = this.projects.filter((p) => p !== project);\n      this.tasks = this.tasks.filter((t) => t.projectId !== project.id);\n\n      if (this.selectedProject === project) {\n        this.selectedProject = null;\n      }\n\n      await Promise.all(projectTasks.map((t) => t.destroyRecord()));\n      await project.destroyRecord();\n      this.notify.success(`Project \"${project.name}\" deleted`);\n    } catch (error) {\n      this.projects = [...this.projects, project];\n      this.notify.error('Failed to delete project');\n    }\n  }\n\n  @action\n  async archiveProject(project) {\n    project.status = 'archived';\n    try {\n      await project.save();\n      this.notify.success(`Project \"${project.name}\" archived`);\n    } catch (error) {\n      project.status = 'active';\n      this.notify.error('Failed to archive project');\n    }\n  }\n\n  // ── Actions: Task CRUD ──────────────────────────────────────────────────\n\n  @action\n  selectTask(task) {\n    this.selectedTask = task;\n  }\n\n  @action\n  clearTaskSelection() {\n    this.selectedTask = null;\n  }\n\n  @action\n  async createTask() {\n    const title = this.newTaskTitle.trim();\n    if (!title) return;\n\n    const task = this.store.createRecord('task', {\n      title,\n      description: this.newTaskDescription.trim(),\n      priority: this.newTaskPriority,\n      assigneeId: this.newTaskAssignee,\n      dueDate: this.newTaskDueDate,\n      labels: [...this.newTaskLabels],\n      estimate: this.newTaskEstimate,\n      projectId: this.selectedProject?.id,\n      status: 'todo',\n      createdAt: new Date(),\n      createdBy: this.session.currentUser.id,\n    });\n\n    try {\n      await task.save();\n      this.tasks = [...this.tasks, task];\n      this._resetTaskForm();\n      this.showCreateModal = false;\n      this._logActivity('task_created', task);\n      this.notify.success(`Task \"${title}\" created`);\n    } catch (error) {\n      task.rollbackAttributes();\n      this.notify.error('Failed to create task');\n    }\n  }\n\n  @action\n  async updateTaskStatus(task, newStatus) {\n    const oldStatus = task.status;\n    task.status = newStatus;\n\n    if (newStatus === 'done') {\n      task.completedAt = new Date();\n    } else if (oldStatus === 'done') {\n      task.completedAt = null;\n    }\n\n    try {\n      await task.save();\n      this._logActivity('task_status_changed', task, { from: oldStatus, to: newStatus });\n    } catch (error) {\n      task.status = oldStatus;\n      this.notify.error('Failed to update task status');\n    }\n  }\n\n  @action\n  async assignTask(task, userId) {\n    const oldAssignee = task.assigneeId;\n    task.assigneeId = userId;\n\n    try {\n      await task.save();\n      this._logActivity('task_assigned', task, { assignee: userId });\n    } catch (error) {\n      task.assigneeId = oldAssignee;\n      this.notify.error('Failed to assign task');\n    }\n  }\n\n  @action\n  async updateTaskPriority(task, priority) {\n    const oldPriority = task.priority;\n    task.priority = priority;\n\n    try {\n      await task.save();\n      this._logActivity('task_priority_changed', task, { from: oldPriority, to: priority });\n    } catch (error) {\n      task.priority = oldPriority;\n      this.notify.error('Failed to update priority');\n    }\n  }\n\n  @action\n  async deleteTask(task) {\n    try {\n      this.tasks = this.tasks.filter((t) => t !== task);\n      if (this.selectedTask === task) {\n        this.selectedTask = null;\n      }\n      await task.destroyRecord();\n      this._logActivity('task_deleted', task);\n      this.notify.success('Task deleted');\n    } catch (error) {\n      this.tasks = [...this.tasks, task];\n      this.notify.error('Failed to delete task');\n    }\n  }\n\n  @action\n  async duplicateTask(task) {\n    const newTask = this.store.createRecord('task', {\n      title: `${task.title} (copy)`,\n      description: task.description,\n      priority: task.priority,\n      assigneeId: task.assigneeId,\n      dueDate: task.dueDate,\n      labels: [...(task.labels || [])],\n      estimate: task.estimate,\n      projectId: task.projectId,\n      status: 'todo',\n      createdAt: new Date(),\n      createdBy: this.session.currentUser.id,\n    });\n\n    try {\n      await newTask.save();\n      this.tasks = [...this.tasks, newTask];\n      this.notify.success('Task duplicated');\n    } catch (error) {\n      newTask.rollbackAttributes();\n      this.notify.error('Failed to duplicate task');\n    }\n  }\n\n  @action\n  async addTaskLabel(task, label) {\n    task.labels = [...(task.labels || []), label];\n    await task.save();\n  }\n\n  @action\n  async removeTaskLabel(task, label) {\n    task.labels = (task.labels || []).filter((l) => l !== label);\n    await task.save();\n  }\n\n  // ── Actions: Comments ───────────────────────────────────────────────────\n\n  @action\n  async addComment(taskId, body) {\n    const comment = this.store.createRecord('comment', {\n      taskId,\n      body: body.trim(),\n      authorId: this.session.currentUser.id,\n      createdAt: new Date(),\n    });\n\n    try {\n      await comment.save();\n      this.comments = [...this.comments, comment];\n      this._logActivity('comment_added', { taskId });\n    } catch (error) {\n      this.notify.error('Failed to add comment');\n    }\n  }\n\n  @action\n  async deleteComment(comment) {\n    this.comments = this.comments.filter((c) => c !== comment);\n    try {\n      await comment.destroyRecord();\n    } catch (error) {\n      this.comments = [...this.comments, comment];\n      this.notify.error('Failed to delete comment');\n    }\n  }\n\n  get taskComments() {\n    if (!this.selectedTask) return [];\n    return this.comments\n      .filter((c) => c.taskId === this.selectedTask.id)\n      .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));\n  }\n\n  // ── Actions: Kanban Board ───────────────────────────────────────────────\n\n  @action\n  onDragStart(task) {\n    this.draggedTask = task;\n  }\n\n  @action\n  onDragOver(column) {\n    this.dragOverColumn = column;\n  }\n\n  @action\n  async onDrop(column) {\n    if (this.draggedTask && this.draggedTask.status !== column) {\n      await this.updateTaskStatus(this.draggedTask, column);\n    }\n    this.draggedTask = null;\n    this.dragOverColumn = null;\n  }\n\n  @action\n  onDragEnd() {\n    this.draggedTask = null;\n    this.dragOverColumn = null;\n  }\n\n  // ── Actions: Time Tracking ──────────────────────────────────────────────\n\n  @action\n  startTimer(taskId) {\n    if (this.isTimerRunning) {\n      this.stopTimer();\n    }\n\n    this.activeTimer = {\n      taskId,\n      startedAt: new Date(),\n      userId: this.session.currentUser.id,\n    };\n    this.timerElapsed = 0;\n\n    this._timerInterval = setInterval(() => {\n      this.timerElapsed = Date.now() - this.activeTimer.startedAt.getTime();\n    }, 1000);\n  }\n\n  @action\n  async stopTimer() {\n    if (!this.activeTimer) return;\n\n    clearInterval(this._timerInterval);\n    this._timerInterval = null;\n\n    const duration = Date.now() - this.activeTimer.startedAt.getTime();\n    const entry = this.store.createRecord('time-entry', {\n      taskId: this.activeTimer.taskId,\n      userId: this.activeTimer.userId,\n      startedAt: this.activeTimer.startedAt,\n      duration,\n    });\n\n    try {\n      await entry.save();\n      this.timeEntries = [...this.timeEntries, entry];\n    } catch (error) {\n      this.notify.error('Failed to save time entry');\n    }\n\n    this.activeTimer = null;\n    this.timerElapsed = 0;\n  }\n\n  @action\n  async deleteTimeEntry(entry) {\n    this.timeEntries = this.timeEntries.filter((e) => e !== entry);\n    try {\n      await entry.destroyRecord();\n    } catch (error) {\n      this.timeEntries = [...this.timeEntries, entry];\n      this.notify.error('Failed to delete time entry');\n    }\n  }\n\n  // ── Actions: Navigation & UI ────────────────────────────────────────────\n\n  @action\n  setTab(tab) {\n    this.activeTab = tab;\n    this.selectedTask = null;\n    this.currentPage = 1;\n  }\n\n  @action\n  updateSearch(event) {\n    this.searchQuery = event.target.value;\n    this.currentPage = 1;\n  }\n\n  @action\n  clearSearch() {\n    this.searchQuery = '';\n    this.currentPage = 1;\n  }\n\n  @action\n  toggleFilters() {\n    this.showFilters = !this.showFilters;\n  }\n\n  @action\n  setFilterStatus(status) {\n    this.filterStatus = status;\n    this.currentPage = 1;\n  }\n\n  @action\n  setFilterAssignee(userId) {\n    this.filterAssignee = userId;\n    this.currentPage = 1;\n  }\n\n  @action\n  setFilterPriority(priority) {\n    this.filterPriority = priority;\n    this.currentPage = 1;\n  }\n\n  @action\n  setDateRange(range) {\n    this.dateRange = range;\n    this.currentPage = 1;\n  }\n\n  @action\n  clearFilters() {\n    this.filterStatus = null;\n    this.filterAssignee = null;\n    this.filterPriority = null;\n    this.dateRange = null;\n    this.currentPage = 1;\n  }\n\n  @action\n  setSortField(field) {\n    if (this.sortField === field) {\n      this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';\n    } else {\n      this.sortField = field;\n      this.sortDirection = 'asc';\n    }\n  }\n\n  @action\n  setViewMode(mode) {\n    this.viewMode = mode;\n  }\n\n  @action\n  setGroupBy(field) {\n    this.groupBy = field;\n  }\n\n  @action\n  toggleCompleted() {\n    this.showCompletedTasks = !this.showCompletedTasks;\n  }\n\n  @action\n  toggleGroupCollapse(groupKey) {\n    this.isCollapsed = {\n      ...this.isCollapsed,\n      [groupKey]: !this.isCollapsed[groupKey],\n    };\n  }\n\n  @action\n  nextPage() {\n    if (this.hasNextPage) this.currentPage++;\n  }\n\n  @action\n  previousPage() {\n    if (this.hasPreviousPage) this.currentPage--;\n  }\n\n  @action\n  goToPage(page) {\n    this.currentPage = Math.max(1, Math.min(page, this.totalPages));\n  }\n\n  @action\n  openCreateModal() {\n    this.showCreateModal = true;\n  }\n\n  @action\n  closeCreateModal() {\n    this.showCreateModal = false;\n    this._resetProjectForm();\n    this._resetTaskForm();\n  }\n\n  @action\n  openEditModal(entity) {\n    this.editingEntity = entity;\n    this.showEditModal = true;\n  }\n\n  @action\n  closeEditModal() {\n    this.editingEntity = null;\n    this.showEditModal = false;\n  }\n\n  @action\n  toggleActivitySidebar() {\n    this.showActivitySidebar = !this.showActivitySidebar;\n  }\n\n  // ── Actions: Import / Export ─────────────────────────────────────────────\n\n  @action\n  exportProjectData() {\n    const project = this.selectedProject;\n    if (!project) return null;\n\n    const tasks = this.currentProjectTasks.map((t) => ({\n      title: t.title,\n      description: t.description,\n      status: t.status,\n      priority: t.priority,\n      assignee: this.users.find((u) => u.id === t.assigneeId)?.name,\n      dueDate: t.dueDate,\n      labels: t.labels,\n      estimate: t.estimate,\n      completedAt: t.completedAt,\n    }));\n\n    return JSON.stringify(\n      {\n        project: {\n          name: project.name,\n          description: project.description,\n          status: project.status,\n        },\n        tasks,\n        exportedAt: new Date().toISOString(),\n        exportedBy: this.session.currentUser.name,\n      },\n      null,\n      2\n    );\n  }\n\n  @action\n  async importProjectData(jsonString) {\n    try {\n      const data = JSON.parse(jsonString);\n      const project = this.store.createRecord('project', {\n        name: data.project.name,\n        description: data.project.description,\n        status: 'active',\n        memberIds: [this.session.currentUser.id],\n        createdAt: new Date(),\n      });\n      await project.save();\n      this.projects = [...this.projects, project];\n\n      for (const taskData of data.tasks || []) {\n        const task = this.store.createRecord('task', {\n          title: taskData.title,\n          description: taskData.description,\n          status: taskData.status || 'todo',\n          priority: taskData.priority || 'medium',\n          labels: taskData.labels || [],\n          estimate: taskData.estimate,\n          projectId: project.id,\n          createdAt: new Date(),\n          createdBy: this.session.currentUser.id,\n        });\n        await task.save();\n        this.tasks = [...this.tasks, task];\n      }\n\n      this.notify.success(\n        `Imported project \"${data.project.name}\" with ${data.tasks.length} tasks`\n      );\n    } catch (error) {\n      this.notify.error('Failed to import project data');\n    }\n  }\n\n  // ── Private Helpers ─────────────────────────────────────────────────────\n\n  _resetProjectForm() {\n    this.newProjectName = '';\n    this.newProjectDescription = '';\n    this.newProjectColor = '#3B82F6';\n    this.newProjectDeadline = null;\n  }\n\n  _resetTaskForm() {\n    this.newTaskTitle = '';\n    this.newTaskDescription = '';\n    this.newTaskPriority = 'medium';\n    this.newTaskAssignee = null;\n    this.newTaskDueDate = null;\n    this.newTaskLabels = [];\n    this.newTaskEstimate = null;\n  }\n\n  _logActivity(type, entity, metadata = {}) {\n    this.activityLog = [\n      {\n        type,\n        entityId: entity.id || entity.taskId,\n        userId: this.session.currentUser.id,\n        timestamp: new Date(),\n        metadata,\n      },\n      ...this.activityLog,\n    ].slice(0, 200);\n  }\n\n  willDestroy() {\n    super.willDestroy();\n    if (this._timerInterval) {\n      clearInterval(this._timerInterval);\n    }\n  }\n}\n"
  },
  {
    "path": "tests/bench/medium.gjs",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { on } from '@ember/modifier';\nimport { fn } from '@ember/helper';\nimport { inject as service } from '@ember/service';\n\nconst PriorityBadge = <template>\n  <span class=\"priority priority--{{@level}}\">\n    {{#if (eq @level \"high\")}}\n      🔴\n    {{else if (eq @level \"medium\")}}\n      🟡\n    {{else}}\n      🟢\n    {{/if}}\n    {{@level}}\n  </span>\n</template>;\n\nconst CategoryTag = <template>\n  <span class=\"tag tag--{{@color}}\">\n    {{@name}}\n    {{#if @onRemove}}\n      <button type=\"button\" class=\"tag__remove\" {{on \"click\" @onRemove}}>×</button>\n    {{/if}}\n  </span>\n</template>;\n\nconst EmptyState = <template>\n  <div class=\"empty-state\">\n    <div class=\"empty-state__icon\">{{@icon}}</div>\n    <h3 class=\"empty-state__title\">{{@title}}</h3>\n    <p class=\"empty-state__description\">{{@description}}</p>\n    {{#if @actionLabel}}\n      <button type=\"button\" class=\"btn btn--primary\" {{on \"click\" @onAction}}>\n        {{@actionLabel}}\n      </button>\n    {{/if}}\n  </div>\n</template>;\n\nconst ProgressBar = <template>\n  <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow={{@value}} aria-valuemin=\"0\" aria-valuemax=\"100\">\n    <div class=\"progress-bar__fill\" style=\"width: {{@value}}%\"></div>\n    <span class=\"progress-bar__label\">{{@value}}%</span>\n  </div>\n</template>;\n\nconst TodoItem = <template>\n  <li class=\"todo-item {{if @item.done 'todo-item--done'}} todo-item--{{@item.priority}}\">\n    <div class=\"todo-item__checkbox\">\n      <input\n        type=\"checkbox\"\n        id=\"todo-{{@item.id}}\"\n        checked={{@item.done}}\n        {{on \"change\" (fn @onToggle @item.id)}}\n      />\n    </div>\n    <div class=\"todo-item__content\">\n      <label for=\"todo-{{@item.id}}\" class=\"todo-item__label\">\n        {{@item.label}}\n      </label>\n      {{#if @item.notes}}\n        <p class=\"todo-item__notes\">{{@item.notes}}</p>\n      {{/if}}\n      <div class=\"todo-item__meta\">\n        <PriorityBadge @level={{@item.priority}} />\n        {{#if @item.category}}\n          <CategoryTag @name={{@item.category}} @color=\"blue\" />\n        {{/if}}\n        {{#if @item.dueDate}}\n          <span class=\"todo-item__due {{if @item.overdue 'todo-item__due--overdue'}}\">\n            Due: {{@item.dueDate}}\n          </span>\n        {{/if}}\n      </div>\n    </div>\n    <div class=\"todo-item__actions\">\n      <button type=\"button\" class=\"btn btn--icon\" {{on \"click\" (fn @onEdit @item)}}>\n        Edit\n      </button>\n      <button type=\"button\" class=\"btn btn--icon btn--danger\" {{on \"click\" (fn @onDelete @item.id)}}>\n        Delete\n      </button>\n    </div>\n  </li>\n</template>;\n\nconst FilterBar = <template>\n  <div class=\"filter-bar\">\n    <div class=\"filter-bar__search\">\n      <input\n        type=\"search\"\n        placeholder=\"Search tasks…\"\n        value={{@searchQuery}}\n        {{on \"input\" @onSearch}}\n      />\n    </div>\n    <div class=\"filter-bar__filters\">\n      <select {{on \"change\" @onFilterChange}}>\n        <option value=\"all\" selected={{eq @filter \"all\"}}>All</option>\n        <option value=\"active\" selected={{eq @filter \"active\"}}>Active</option>\n        <option value=\"completed\" selected={{eq @filter \"completed\"}}>Completed</option>\n        <option value=\"high-priority\" selected={{eq @filter \"high-priority\"}}>High Priority</option>\n        <option value=\"overdue\" selected={{eq @filter \"overdue\"}}>Overdue</option>\n      </select>\n      <select {{on \"change\" @onSortChange}}>\n        <option value=\"createdAt\" selected={{eq @sortBy \"createdAt\"}}>Date Created</option>\n        <option value=\"label\" selected={{eq @sortBy \"label\"}}>Name</option>\n        <option value=\"priority\" selected={{eq @sortBy \"priority\"}}>Priority</option>\n        <option value=\"dueDate\" selected={{eq @sortBy \"dueDate\"}}>Due Date</option>\n      </select>\n    </div>\n    <div class=\"filter-bar__actions\">\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onClearFilters}}>\n        Clear Filters\n      </button>\n    </div>\n  </div>\n</template>;\n\nconst StatsPanel = <template>\n  <div class=\"stats-panel\">\n    <div class=\"stats-panel__item\">\n      <span class=\"stats-panel__value\">{{@stats.total}}</span>\n      <span class=\"stats-panel__label\">Total</span>\n    </div>\n    <div class=\"stats-panel__item\">\n      <span class=\"stats-panel__value\">{{@stats.active}}</span>\n      <span class=\"stats-panel__label\">Active</span>\n    </div>\n    <div class=\"stats-panel__item\">\n      <span class=\"stats-panel__value\">{{@stats.completed}}</span>\n      <span class=\"stats-panel__label\">Completed</span>\n    </div>\n    <div class=\"stats-panel__item stats-panel__item--warning\">\n      <span class=\"stats-panel__value\">{{@stats.overdue}}</span>\n      <span class=\"stats-panel__label\">Overdue</span>\n    </div>\n    <div class=\"stats-panel__progress\">\n      <ProgressBar @value={{@stats.progress}} />\n    </div>\n  </div>\n</template>;\n\nconst BulkActionsBar = <template>\n  {{#if @visible}}\n    <div class=\"bulk-actions\">\n      <span class=\"bulk-actions__count\">{{@count}} selected</span>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onComplete}}>\n        Mark Complete\n      </button>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onSetHighPriority}}>\n        Set High Priority\n      </button>\n      <button type=\"button\" class=\"btn btn--sm btn--danger\" {{on \"click\" @onDelete}}>\n        Delete Selected\n      </button>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onClear}}>\n        Clear Selection\n      </button>\n    </div>\n  {{/if}}\n</template>;\n\nconst AddItemForm = <template>\n  <form class=\"add-form\" {{on \"submit\" @onSubmit}}>\n    <div class=\"add-form__row\">\n      <input\n        type=\"text\"\n        class=\"add-form__input\"\n        placeholder=\"What needs to be done?\"\n        value={{@label}}\n        {{on \"input\" @onLabelChange}}\n        {{on \"keydown\" @onKeydown}}\n      />\n      <button type=\"submit\" class=\"btn btn--primary\" disabled={{@isLoading}}>\n        {{#if @isLoading}}\n          Adding…\n        {{else}}\n          Add Task\n        {{/if}}\n      </button>\n    </div>\n    <div class=\"add-form__options\">\n      <select {{on \"change\" @onPriorityChange}}>\n        <option value=\"low\">Low Priority</option>\n        <option value=\"normal\" selected>Normal Priority</option>\n        <option value=\"high\">High Priority</option>\n      </select>\n      <select {{on \"change\" @onCategoryChange}}>\n        <option value=\"\">No Category</option>\n        {{#each @categories as |cat|}}\n          <option value={{cat.id}}>{{cat.name}}</option>\n        {{/each}}\n      </select>\n      <input\n        type=\"date\"\n        value={{@dueDate}}\n        {{on \"change\" @onDueDateChange}}\n      />\n    </div>\n    {{#if @error}}\n      <div class=\"add-form__error\" role=\"alert\">{{@error}}</div>\n    {{/if}}\n  </form>\n</template>;\n\nexport default class TodoList extends Component {\n  @service store;\n  @service('notifications') notify;\n\n  @tracked items = [];\n  @tracked newLabel = '';\n  @tracked filter = 'all';\n  @tracked sortBy = 'createdAt';\n  @tracked searchQuery = '';\n  @tracked isLoading = false;\n  @tracked errorMessage = null;\n  @tracked selectedItems = new Set();\n  @tracked priority = 'normal';\n  @tracked selectedCategory = null;\n  @tracked dueDate = null;\n\n  get filteredItems() {\n    let result = this.items;\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter((i) => i.label.toLowerCase().includes(q));\n    }\n    switch (this.filter) {\n      case 'active':\n        return result.filter((i) => !i.done);\n      case 'completed':\n        return result.filter((i) => i.done);\n      case 'high-priority':\n        return result.filter((i) => i.priority === 'high');\n      case 'overdue':\n        return result.filter((i) => i.dueDate && new Date(i.dueDate) < new Date() && !i.done);\n      default:\n        return result;\n    }\n  }\n\n  get sortedItems() {\n    return [...this.filteredItems].sort((a, b) => {\n      if (a[this.sortBy] < b[this.sortBy]) return -1;\n      if (a[this.sortBy] > b[this.sortBy]) return 1;\n      return 0;\n    });\n  }\n\n  get stats() {\n    return {\n      total: this.items.length,\n      active: this.items.filter((i) => !i.done).length,\n      completed: this.items.filter((i) => i.done).length,\n      overdue: this.items.filter(\n        (i) => i.dueDate && new Date(i.dueDate) < new Date() && !i.done,\n      ).length,\n      progress:\n        this.items.length > 0\n          ? Math.round(\n              (this.items.filter((i) => i.done).length / this.items.length) * 100,\n            )\n          : 0,\n    };\n  }\n\n  get hasItems() {\n    return this.items.length > 0;\n  }\n\n  get showBulkActions() {\n    return this.selectedItems.size > 0;\n  }\n\n  @action addItem(event) {\n    event.preventDefault();\n    const label = this.newLabel.trim();\n    if (!label) return;\n    this.items = [\n      ...this.items,\n      { id: Date.now(), label, done: false, priority: this.priority, category: this.selectedCategory, dueDate: this.dueDate, notes: '', createdAt: new Date() },\n    ];\n    this.newLabel = '';\n    this.priority = 'normal';\n    this.dueDate = null;\n  }\n\n  @action toggleItem(id) {\n    this.items = this.items.map((i) => (i.id === id ? { ...i, done: !i.done } : i));\n  }\n\n  @action deleteItem(id) {\n    this.items = this.items.filter((i) => i.id !== id);\n    this.selectedItems.delete(id);\n  }\n\n  @action editItem(item) {\n    this.notify.info(`Editing: ${item.label}`);\n  }\n\n  @action updateLabel(event) { this.newLabel = event.target.value; }\n  @action handleKeydown(event) { if (event.key === 'Escape') this.newLabel = ''; }\n  @action updateSearch(event) { this.searchQuery = event.target.value; }\n  @action setFilter(event) { this.filter = event.target.value; }\n  @action setSort(event) { this.sortBy = event.target.value; }\n  @action setPriority(event) { this.priority = event.target.value; }\n  @action setCategory(event) { this.selectedCategory = event.target.value || null; }\n  @action setDueDate(event) { this.dueDate = event.target.value || null; }\n  @action clearFilters() { this.filter = 'all'; this.searchQuery = ''; this.sortBy = 'createdAt'; }\n  @action clearSelection() { this.selectedItems = new Set(); }\n\n  @action bulkComplete() {\n    this.items = this.items.map((i) => (this.selectedItems.has(i.id) ? { ...i, done: true } : i));\n    this.selectedItems = new Set();\n  }\n\n  @action bulkDelete() {\n    this.items = this.items.filter((i) => !this.selectedItems.has(i.id));\n    this.selectedItems = new Set();\n  }\n\n  @action bulkSetHighPriority() {\n    this.items = this.items.map((i) => (this.selectedItems.has(i.id) ? { ...i, priority: 'high' } : i));\n    this.selectedItems = new Set();\n  }\n\n  <template>\n    <div class=\"todo-app\">\n      <header class=\"todo-app__header\">\n        <h1>{{@title}}</h1>\n        <StatsPanel @stats={{this.stats}} />\n      </header>\n\n      <AddItemForm\n        @label={{this.newLabel}}\n        @isLoading={{this.isLoading}}\n        @error={{this.errorMessage}}\n        @categories={{@categories}}\n        @dueDate={{this.dueDate}}\n        @onSubmit={{this.addItem}}\n        @onLabelChange={{this.updateLabel}}\n        @onKeydown={{this.handleKeydown}}\n        @onPriorityChange={{this.setPriority}}\n        @onCategoryChange={{this.setCategory}}\n        @onDueDateChange={{this.setDueDate}}\n      />\n\n      <FilterBar\n        @searchQuery={{this.searchQuery}}\n        @filter={{this.filter}}\n        @sortBy={{this.sortBy}}\n        @onSearch={{this.updateSearch}}\n        @onFilterChange={{this.setFilter}}\n        @onSortChange={{this.setSort}}\n        @onClearFilters={{this.clearFilters}}\n      />\n\n      <BulkActionsBar\n        @visible={{this.showBulkActions}}\n        @count={{this.selectedItems.size}}\n        @onComplete={{this.bulkComplete}}\n        @onSetHighPriority={{this.bulkSetHighPriority}}\n        @onDelete={{this.bulkDelete}}\n        @onClear={{this.clearSelection}}\n      />\n\n      {{#if this.hasItems}}\n        <ul class=\"todo-list\">\n          {{#each this.sortedItems as |item|}}\n            <TodoItem\n              @item={{item}}\n              @onToggle={{this.toggleItem}}\n              @onEdit={{this.editItem}}\n              @onDelete={{this.deleteItem}}\n            />\n          {{/each}}\n        </ul>\n      {{else}}\n        <EmptyState\n          @icon=\"📝\"\n          @title=\"No tasks yet\"\n          @description=\"Add your first task above to get started.\"\n        />\n      {{/if}}\n    </div>\n  </template>\n}\n"
  },
  {
    "path": "tests/bench/medium.gts",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { on } from '@ember/modifier';\nimport { fn } from '@ember/helper';\nimport { inject as service } from '@ember/service';\nimport type { TOC } from '@ember/component/template-only';\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\ninterface TodoItemData {\n  id: number;\n  label: string;\n  done: boolean;\n  priority: 'low' | 'normal' | 'high';\n  category: string | null;\n  dueDate: string | null;\n  notes: string;\n  overdue?: boolean;\n  createdAt: Date;\n}\n\ninterface CategoryData {\n  id: string;\n  name: string;\n  color: string;\n}\n\ninterface TodoStats {\n  total: number;\n  active: number;\n  completed: number;\n  overdue: number;\n  progress: number;\n}\n\n// ── Presentational Components ─────────────────────────────────────────────\n\nconst PriorityBadge: TOC<{ Args: { level: TodoItemData['priority'] } }> = <template>\n  <span class=\"priority priority--{{@level}}\">\n    {{#if (eq @level \"high\")}}\n      🔴\n    {{else if (eq @level \"medium\")}}\n      🟡\n    {{else}}\n      🟢\n    {{/if}}\n    {{@level}}\n  </span>\n</template>;\n\nconst CategoryTag: TOC<{ Args: { name: string; color: string; onRemove?: () => void } }> = <template>\n  <span class=\"tag tag--{{@color}}\">\n    {{@name}}\n    {{#if @onRemove}}\n      <button type=\"button\" class=\"tag__remove\" {{on \"click\" @onRemove}}>×</button>\n    {{/if}}\n  </span>\n</template>;\n\nconst EmptyState: TOC<{ Args: { icon: string; title: string; description: string; actionLabel?: string; onAction?: () => void } }> = <template>\n  <div class=\"empty-state\">\n    <div class=\"empty-state__icon\">{{@icon}}</div>\n    <h3 class=\"empty-state__title\">{{@title}}</h3>\n    <p class=\"empty-state__description\">{{@description}}</p>\n    {{#if @actionLabel}}\n      <button type=\"button\" class=\"btn btn--primary\" {{on \"click\" @onAction}}>\n        {{@actionLabel}}\n      </button>\n    {{/if}}\n  </div>\n</template>;\n\nconst ProgressBar: TOC<{ Args: { value: number } }> = <template>\n  <div class=\"progress-bar\" role=\"progressbar\" aria-valuenow={{@value}} aria-valuemin=\"0\" aria-valuemax=\"100\">\n    <div class=\"progress-bar__fill\" style=\"width: {{@value}}%\"></div>\n    <span class=\"progress-bar__label\">{{@value}}%</span>\n  </div>\n</template>;\n\nconst TodoItem: TOC<{ Args: { item: TodoItemData; onToggle: (id: number) => void; onEdit: (item: TodoItemData) => void; onDelete: (id: number) => void } }> = <template>\n  <li class=\"todo-item {{if @item.done 'todo-item--done'}} todo-item--{{@item.priority}}\">\n    <div class=\"todo-item__checkbox\">\n      <input\n        type=\"checkbox\"\n        id=\"todo-{{@item.id}}\"\n        checked={{@item.done}}\n        {{on \"change\" (fn @onToggle @item.id)}}\n      />\n    </div>\n    <div class=\"todo-item__content\">\n      <label for=\"todo-{{@item.id}}\" class=\"todo-item__label\">\n        {{@item.label}}\n      </label>\n      {{#if @item.notes}}\n        <p class=\"todo-item__notes\">{{@item.notes}}</p>\n      {{/if}}\n      <div class=\"todo-item__meta\">\n        <PriorityBadge @level={{@item.priority}} />\n        {{#if @item.category}}\n          <CategoryTag @name={{@item.category}} @color=\"blue\" />\n        {{/if}}\n        {{#if @item.dueDate}}\n          <span class=\"todo-item__due {{if @item.overdue 'todo-item__due--overdue'}}\">\n            Due: {{@item.dueDate}}\n          </span>\n        {{/if}}\n      </div>\n    </div>\n    <div class=\"todo-item__actions\">\n      <button type=\"button\" class=\"btn btn--icon\" {{on \"click\" (fn @onEdit @item)}}>\n        Edit\n      </button>\n      <button type=\"button\" class=\"btn btn--icon btn--danger\" {{on \"click\" (fn @onDelete @item.id)}}>\n        Delete\n      </button>\n    </div>\n  </li>\n</template>;\n\nconst FilterBar: TOC<{ Args: { searchQuery: string; filter: string; sortBy: string; onSearch: (e: Event) => void; onFilterChange: (e: Event) => void; onSortChange: (e: Event) => void; onClearFilters: () => void } }> = <template>\n  <div class=\"filter-bar\">\n    <div class=\"filter-bar__search\">\n      <input\n        type=\"search\"\n        placeholder=\"Search tasks…\"\n        value={{@searchQuery}}\n        {{on \"input\" @onSearch}}\n      />\n    </div>\n    <div class=\"filter-bar__filters\">\n      <select {{on \"change\" @onFilterChange}}>\n        <option value=\"all\" selected={{eq @filter \"all\"}}>All</option>\n        <option value=\"active\" selected={{eq @filter \"active\"}}>Active</option>\n        <option value=\"completed\" selected={{eq @filter \"completed\"}}>Completed</option>\n        <option value=\"high-priority\" selected={{eq @filter \"high-priority\"}}>High Priority</option>\n        <option value=\"overdue\" selected={{eq @filter \"overdue\"}}>Overdue</option>\n      </select>\n      <select {{on \"change\" @onSortChange}}>\n        <option value=\"createdAt\" selected={{eq @sortBy \"createdAt\"}}>Date Created</option>\n        <option value=\"label\" selected={{eq @sortBy \"label\"}}>Name</option>\n        <option value=\"priority\" selected={{eq @sortBy \"priority\"}}>Priority</option>\n        <option value=\"dueDate\" selected={{eq @sortBy \"dueDate\"}}>Due Date</option>\n      </select>\n    </div>\n    <div class=\"filter-bar__actions\">\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onClearFilters}}>\n        Clear Filters\n      </button>\n    </div>\n  </div>\n</template>;\n\nconst StatsPanel: TOC<{ Args: { stats: TodoStats } }> = <template>\n  <div class=\"stats-panel\">\n    <div class=\"stats-panel__item\">\n      <span class=\"stats-panel__value\">{{@stats.total}}</span>\n      <span class=\"stats-panel__label\">Total</span>\n    </div>\n    <div class=\"stats-panel__item\">\n      <span class=\"stats-panel__value\">{{@stats.active}}</span>\n      <span class=\"stats-panel__label\">Active</span>\n    </div>\n    <div class=\"stats-panel__item\">\n      <span class=\"stats-panel__value\">{{@stats.completed}}</span>\n      <span class=\"stats-panel__label\">Completed</span>\n    </div>\n    <div class=\"stats-panel__item stats-panel__item--warning\">\n      <span class=\"stats-panel__value\">{{@stats.overdue}}</span>\n      <span class=\"stats-panel__label\">Overdue</span>\n    </div>\n    <div class=\"stats-panel__progress\">\n      <ProgressBar @value={{@stats.progress}} />\n    </div>\n  </div>\n</template>;\n\nconst BulkActionsBar: TOC<{ Args: { visible: boolean; count: number; onComplete: () => void; onSetHighPriority: () => void; onDelete: () => void; onClear: () => void } }> = <template>\n  {{#if @visible}}\n    <div class=\"bulk-actions\">\n      <span class=\"bulk-actions__count\">{{@count}} selected</span>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onComplete}}>\n        Mark Complete\n      </button>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onSetHighPriority}}>\n        Set High Priority\n      </button>\n      <button type=\"button\" class=\"btn btn--sm btn--danger\" {{on \"click\" @onDelete}}>\n        Delete Selected\n      </button>\n      <button type=\"button\" class=\"btn btn--sm\" {{on \"click\" @onClear}}>\n        Clear Selection\n      </button>\n    </div>\n  {{/if}}\n</template>;\n\nconst AddItemForm: TOC<{ Args: { label: string; isLoading: boolean; error: string | null; categories: CategoryData[]; dueDate: string | null; onSubmit: (e: Event) => void; onLabelChange: (e: Event) => void; onKeydown: (e: KeyboardEvent) => void; onPriorityChange: (e: Event) => void; onCategoryChange: (e: Event) => void; onDueDateChange: (e: Event) => void } }> = <template>\n  <form class=\"add-form\" {{on \"submit\" @onSubmit}}>\n    <div class=\"add-form__row\">\n      <input\n        type=\"text\"\n        class=\"add-form__input\"\n        placeholder=\"What needs to be done?\"\n        value={{@label}}\n        {{on \"input\" @onLabelChange}}\n        {{on \"keydown\" @onKeydown}}\n      />\n      <button type=\"submit\" class=\"btn btn--primary\" disabled={{@isLoading}}>\n        {{#if @isLoading}}\n          Adding…\n        {{else}}\n          Add Task\n        {{/if}}\n      </button>\n    </div>\n    <div class=\"add-form__options\">\n      <select {{on \"change\" @onPriorityChange}}>\n        <option value=\"low\">Low Priority</option>\n        <option value=\"normal\" selected>Normal Priority</option>\n        <option value=\"high\">High Priority</option>\n      </select>\n      <select {{on \"change\" @onCategoryChange}}>\n        <option value=\"\">No Category</option>\n        {{#each @categories as |cat|}}\n          <option value={{cat.id}}>{{cat.name}}</option>\n        {{/each}}\n      </select>\n      <input\n        type=\"date\"\n        value={{@dueDate}}\n        {{on \"change\" @onDueDateChange}}\n      />\n    </div>\n    {{#if @error}}\n      <div class=\"add-form__error\" role=\"alert\">{{@error}}</div>\n    {{/if}}\n  </form>\n</template>;\n\n// ── Main Component ────────────────────────────────────────────────────────\n\ninterface TodoListArgs {\n  title: string;\n  categories: CategoryData[];\n}\n\nexport default class TodoList extends Component<{ Args: TodoListArgs }> {\n  @service declare store: any;\n  @service('notifications') declare notify: any;\n\n  @tracked items: TodoItemData[] = [];\n  @tracked newLabel = '';\n  @tracked filter = 'all';\n  @tracked sortBy = 'createdAt';\n  @tracked searchQuery = '';\n  @tracked isLoading = false;\n  @tracked errorMessage: string | null = null;\n  @tracked selectedItems = new Set<number>();\n  @tracked priority: TodoItemData['priority'] = 'normal';\n  @tracked selectedCategory: string | null = null;\n  @tracked dueDate: string | null = null;\n\n  get filteredItems(): TodoItemData[] {\n    let result = this.items;\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter((i) => i.label.toLowerCase().includes(q));\n    }\n    switch (this.filter) {\n      case 'active':\n        return result.filter((i) => !i.done);\n      case 'completed':\n        return result.filter((i) => i.done);\n      case 'high-priority':\n        return result.filter((i) => i.priority === 'high');\n      case 'overdue':\n        return result.filter((i) => i.dueDate && new Date(i.dueDate) < new Date() && !i.done);\n      default:\n        return result;\n    }\n  }\n\n  get sortedItems(): TodoItemData[] {\n    return [...this.filteredItems].sort((a, b) => {\n      const aVal = a[this.sortBy as keyof TodoItemData];\n      const bVal = b[this.sortBy as keyof TodoItemData];\n      if (aVal! < bVal!) return -1;\n      if (aVal! > bVal!) return 1;\n      return 0;\n    });\n  }\n\n  get stats(): TodoStats {\n    return {\n      total: this.items.length,\n      active: this.items.filter((i) => !i.done).length,\n      completed: this.items.filter((i) => i.done).length,\n      overdue: this.items.filter(\n        (i) => i.dueDate && new Date(i.dueDate) < new Date() && !i.done,\n      ).length,\n      progress:\n        this.items.length > 0\n          ? Math.round(\n              (this.items.filter((i) => i.done).length / this.items.length) * 100,\n            )\n          : 0,\n    };\n  }\n\n  get hasItems(): boolean {\n    return this.items.length > 0;\n  }\n\n  get showBulkActions(): boolean {\n    return this.selectedItems.size > 0;\n  }\n\n  @action addItem(event: Event) {\n    event.preventDefault();\n    const label = this.newLabel.trim();\n    if (!label) return;\n    this.items = [\n      ...this.items,\n      { id: Date.now(), label, done: false, priority: this.priority, category: this.selectedCategory, dueDate: this.dueDate, notes: '', createdAt: new Date() },\n    ];\n    this.newLabel = '';\n    this.priority = 'normal';\n    this.dueDate = null;\n  }\n\n  @action toggleItem(id: number) {\n    this.items = this.items.map((i) => (i.id === id ? { ...i, done: !i.done } : i));\n  }\n\n  @action deleteItem(id: number) {\n    this.items = this.items.filter((i) => i.id !== id);\n    this.selectedItems.delete(id);\n  }\n\n  @action editItem(item: TodoItemData) {\n    this.notify.info(`Editing: ${item.label}`);\n  }\n\n  @action updateLabel(event: Event) { this.newLabel = (event.target as HTMLInputElement).value; }\n  @action handleKeydown(event: KeyboardEvent) { if (event.key === 'Escape') this.newLabel = ''; }\n  @action updateSearch(event: Event) { this.searchQuery = (event.target as HTMLInputElement).value; }\n  @action setFilter(event: Event) { this.filter = (event.target as HTMLSelectElement).value; }\n  @action setSort(event: Event) { this.sortBy = (event.target as HTMLSelectElement).value; }\n  @action setPriority(event: Event) { this.priority = (event.target as HTMLSelectElement).value as TodoItemData['priority']; }\n  @action setCategory(event: Event) { this.selectedCategory = (event.target as HTMLSelectElement).value || null; }\n  @action setDueDate(event: Event) { this.dueDate = (event.target as HTMLInputElement).value || null; }\n  @action clearFilters() { this.filter = 'all'; this.searchQuery = ''; this.sortBy = 'createdAt'; }\n  @action clearSelection() { this.selectedItems = new Set(); }\n\n  @action bulkComplete() {\n    this.items = this.items.map((i) => (this.selectedItems.has(i.id) ? { ...i, done: true } : i));\n    this.selectedItems = new Set();\n  }\n\n  @action bulkDelete() {\n    this.items = this.items.filter((i) => !this.selectedItems.has(i.id));\n    this.selectedItems = new Set();\n  }\n\n  @action bulkSetHighPriority() {\n    this.items = this.items.map((i) => (this.selectedItems.has(i.id) ? { ...i, priority: 'high' as const } : i));\n    this.selectedItems = new Set();\n  }\n\n  <template>\n    <div class=\"todo-app\">\n      <header class=\"todo-app__header\">\n        <h1>{{@title}}</h1>\n        <StatsPanel @stats={{this.stats}} />\n      </header>\n\n      <AddItemForm\n        @label={{this.newLabel}}\n        @isLoading={{this.isLoading}}\n        @error={{this.errorMessage}}\n        @categories={{@categories}}\n        @dueDate={{this.dueDate}}\n        @onSubmit={{this.addItem}}\n        @onLabelChange={{this.updateLabel}}\n        @onKeydown={{this.handleKeydown}}\n        @onPriorityChange={{this.setPriority}}\n        @onCategoryChange={{this.setCategory}}\n        @onDueDateChange={{this.setDueDate}}\n      />\n\n      <FilterBar\n        @searchQuery={{this.searchQuery}}\n        @filter={{this.filter}}\n        @sortBy={{this.sortBy}}\n        @onSearch={{this.updateSearch}}\n        @onFilterChange={{this.setFilter}}\n        @onSortChange={{this.setSort}}\n        @onClearFilters={{this.clearFilters}}\n      />\n\n      <BulkActionsBar\n        @visible={{this.showBulkActions}}\n        @count={{this.selectedItems.size}}\n        @onComplete={{this.bulkComplete}}\n        @onSetHighPriority={{this.bulkSetHighPriority}}\n        @onDelete={{this.bulkDelete}}\n        @onClear={{this.clearSelection}}\n      />\n\n      {{#if this.hasItems}}\n        <ul class=\"todo-list\">\n          {{#each this.sortedItems as |item|}}\n            <TodoItem\n              @item={{item}}\n              @onToggle={{this.toggleItem}}\n              @onEdit={{this.editItem}}\n              @onDelete={{this.deleteItem}}\n            />\n          {{/each}}\n        </ul>\n      {{else}}\n        <EmptyState\n          @icon=\"📝\"\n          @title=\"No tasks yet\"\n          @description=\"Add your first task above to get started.\"\n        />\n      {{/if}}\n    </div>\n  </template>\n}\n"
  },
  {
    "path": "tests/bench/medium.js",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { inject as service } from '@ember/service';\nimport { later, cancel } from '@ember/runloop';\n\nexport default class TodoListComponent extends Component {\n  @service store;\n  @service router;\n  @service('notifications') notify;\n\n  @tracked items = [];\n  @tracked newLabel = '';\n  @tracked filter = 'all';\n  @tracked isLoading = false;\n  @tracked errorMessage = null;\n  @tracked editingItem = null;\n  @tracked editText = '';\n  @tracked sortBy = 'createdAt';\n  @tracked sortOrder = 'desc';\n  @tracked selectedItems = new Set();\n  @tracked showBulkActions = false;\n  @tracked searchQuery = '';\n  @tracked categories = [];\n  @tracked selectedCategory = null;\n  @tracked dueDate = null;\n  @tracked priority = 'normal';\n  @tracked isArchiveView = false;\n  @tracked archivedItems = [];\n  @tracked undoStack = [];\n  @tracked redoStack = [];\n\n  get filteredItems() {\n    let result = this.isArchiveView ? this.archivedItems : this.items;\n\n    if (this.searchQuery) {\n      const q = this.searchQuery.toLowerCase();\n      result = result.filter(\n        (item) =>\n          item.label.toLowerCase().includes(q) ||\n          (item.notes && item.notes.toLowerCase().includes(q))\n      );\n    }\n\n    if (this.selectedCategory) {\n      result = result.filter((item) => item.category === this.selectedCategory);\n    }\n\n    switch (this.filter) {\n      case 'active':\n        return result.filter((item) => !item.done);\n      case 'completed':\n        return result.filter((item) => item.done);\n      case 'high-priority':\n        return result.filter((item) => item.priority === 'high');\n      case 'overdue':\n        return result.filter(\n          (item) => item.dueDate && new Date(item.dueDate) < new Date() && !item.done\n        );\n      default:\n        return result;\n    }\n  }\n\n  get sortedItems() {\n    const items = [...this.filteredItems];\n    items.sort((a, b) => {\n      let aVal = a[this.sortBy];\n      let bVal = b[this.sortBy];\n\n      if (this.sortBy === 'priority') {\n        const priorityOrder = { high: 0, normal: 1, low: 2 };\n        aVal = priorityOrder[aVal] ?? 1;\n        bVal = priorityOrder[bVal] ?? 1;\n      }\n\n      if (aVal < bVal) return this.sortOrder === 'asc' ? -1 : 1;\n      if (aVal > bVal) return this.sortOrder === 'asc' ? 1 : -1;\n      return 0;\n    });\n    return items;\n  }\n\n  get completedCount() {\n    return this.items.filter((i) => i.done).length;\n  }\n\n  get activeCount() {\n    return this.items.length - this.completedCount;\n  }\n\n  get hasCompleted() {\n    return this.completedCount > 0;\n  }\n\n  get allCompleted() {\n    return this.items.length > 0 && this.completedCount === this.items.length;\n  }\n\n  get selectedCount() {\n    return this.selectedItems.size;\n  }\n\n  get hasSelection() {\n    return this.selectedCount > 0;\n  }\n\n  get allSelected() {\n    return (\n      this.filteredItems.length > 0 &&\n      this.filteredItems.every((item) => this.selectedItems.has(item.id))\n    );\n  }\n\n  get overdueCount() {\n    return this.items.filter(\n      (item) => item.dueDate && new Date(item.dueDate) < new Date() && !item.done\n    ).length;\n  }\n\n  get itemsByCategory() {\n    const grouped = {};\n    for (const item of this.items) {\n      const cat = item.category || 'Uncategorized';\n      if (!grouped[cat]) grouped[cat] = [];\n      grouped[cat].push(item);\n    }\n    return grouped;\n  }\n\n  get canUndo() {\n    return this.undoStack.length > 0;\n  }\n\n  get canRedo() {\n    return this.redoStack.length > 0;\n  }\n\n  get progressPercentage() {\n    if (this.items.length === 0) return 0;\n    return Math.round((this.completedCount / this.items.length) * 100);\n  }\n\n  get statsForDisplay() {\n    return {\n      total: this.items.length,\n      active: this.activeCount,\n      completed: this.completedCount,\n      overdue: this.overdueCount,\n      archived: this.archivedItems.length,\n      progress: this.progressPercentage,\n    };\n  }\n\n  _pushUndo(actionDescription) {\n    this.undoStack = [\n      ...this.undoStack,\n      {\n        description: actionDescription,\n        snapshot: JSON.parse(JSON.stringify(this.items)),\n      },\n    ];\n    this.redoStack = [];\n  }\n\n  @action\n  async addItem() {\n    const label = this.newLabel.trim();\n    if (!label) return;\n\n    this.isLoading = true;\n    this.errorMessage = null;\n    this._pushUndo('add item');\n\n    try {\n      const record = this.store.createRecord('todo', {\n        label,\n        done: false,\n        priority: this.priority,\n        category: this.selectedCategory,\n        dueDate: this.dueDate,\n        notes: '',\n        createdAt: new Date(),\n      });\n      await record.save();\n      this.items = [...this.items, record];\n      this.newLabel = '';\n      this.priority = 'normal';\n      this.dueDate = null;\n      this.notify.success(`Added \"${label}\"`);\n    } catch (error) {\n      this.errorMessage = error.message;\n      this.notify.error('Failed to add item');\n    } finally {\n      this.isLoading = false;\n    }\n  }\n\n  @action\n  toggleItem(item) {\n    this._pushUndo('toggle item');\n    item.done = !item.done;\n    item.save();\n  }\n\n  @action\n  removeItem(item) {\n    this._pushUndo('remove item');\n    this.items = this.items.filter((i) => i !== item);\n    this.selectedItems.delete(item.id);\n    item.destroyRecord();\n  }\n\n  @action\n  clearCompleted() {\n    this._pushUndo('clear completed');\n    const completed = this.items.filter((i) => i.done);\n    this.items = this.items.filter((i) => !i.done);\n    completed.forEach((item) => {\n      this.selectedItems.delete(item.id);\n      item.destroyRecord();\n    });\n  }\n\n  @action\n  setFilter(filter) {\n    this.filter = filter;\n  }\n\n  @action\n  updateLabel(event) {\n    this.newLabel = event.target.value;\n  }\n\n  @action\n  handleKeydown(event) {\n    if (event.key === 'Enter') {\n      this.addItem();\n    } else if (event.key === 'Escape') {\n      this.newLabel = '';\n    }\n  }\n\n  @action\n  toggleAll() {\n    this._pushUndo('toggle all');\n    const allDone = this.allCompleted;\n    this.items.forEach((item) => {\n      item.done = !allDone;\n      item.save();\n    });\n  }\n\n  @action\n  startEditing(item) {\n    this.editingItem = item;\n    this.editText = item.label;\n  }\n\n  @action\n  cancelEditing() {\n    this.editingItem = null;\n    this.editText = '';\n  }\n\n  @action\n  async saveEdit() {\n    if (!this.editingItem) return;\n    const text = this.editText.trim();\n    if (!text) return;\n\n    this._pushUndo('edit item');\n    this.editingItem.label = text;\n    await this.editingItem.save();\n    this.editingItem = null;\n    this.editText = '';\n  }\n\n  @action\n  toggleSelection(itemId) {\n    const next = new Set(this.selectedItems);\n    if (next.has(itemId)) {\n      next.delete(itemId);\n    } else {\n      next.add(itemId);\n    }\n    this.selectedItems = next;\n    this.showBulkActions = next.size > 0;\n  }\n\n  @action\n  selectAll() {\n    if (this.allSelected) {\n      this.selectedItems = new Set();\n    } else {\n      this.selectedItems = new Set(this.filteredItems.map((i) => i.id));\n    }\n    this.showBulkActions = this.selectedItems.size > 0;\n  }\n\n  @action\n  bulkDelete() {\n    this._pushUndo('bulk delete');\n    const ids = this.selectedItems;\n    const toDelete = this.items.filter((i) => ids.has(i.id));\n    this.items = this.items.filter((i) => !ids.has(i.id));\n    this.selectedItems = new Set();\n    this.showBulkActions = false;\n    toDelete.forEach((item) => item.destroyRecord());\n  }\n\n  @action\n  bulkComplete() {\n    this._pushUndo('bulk complete');\n    const ids = this.selectedItems;\n    this.items.forEach((item) => {\n      if (ids.has(item.id)) {\n        item.done = true;\n        item.save();\n      }\n    });\n  }\n\n  @action\n  bulkSetPriority(priority) {\n    this._pushUndo('bulk set priority');\n    const ids = this.selectedItems;\n    this.items.forEach((item) => {\n      if (ids.has(item.id)) {\n        item.priority = priority;\n        item.save();\n      }\n    });\n  }\n\n  @action\n  archiveCompleted() {\n    this._pushUndo('archive completed');\n    const completed = this.items.filter((i) => i.done);\n    this.archivedItems = [...this.archivedItems, ...completed];\n    this.items = this.items.filter((i) => !i.done);\n  }\n\n  @action\n  restoreFromArchive(item) {\n    item.done = false;\n    this.archivedItems = this.archivedItems.filter((i) => i !== item);\n    this.items = [...this.items, item];\n    item.save();\n  }\n\n  @action\n  toggleArchiveView() {\n    this.isArchiveView = !this.isArchiveView;\n  }\n\n  @action\n  setSortBy(field) {\n    if (this.sortBy === field) {\n      this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';\n    } else {\n      this.sortBy = field;\n      this.sortOrder = 'asc';\n    }\n  }\n\n  @action\n  updateSearch(event) {\n    this.searchQuery = event.target.value;\n  }\n\n  @action\n  setCategory(category) {\n    this.selectedCategory = category;\n  }\n\n  @action\n  setPriority(priority) {\n    this.priority = priority;\n  }\n\n  @action\n  setDueDate(date) {\n    this.dueDate = date;\n  }\n\n  @action\n  undo() {\n    if (!this.canUndo) return;\n    const entry = this.undoStack[this.undoStack.length - 1];\n    this.redoStack = [\n      ...this.redoStack,\n      {\n        description: entry.description,\n        snapshot: JSON.parse(JSON.stringify(this.items)),\n      },\n    ];\n    this.undoStack = this.undoStack.slice(0, -1);\n    this.items = entry.snapshot;\n  }\n\n  @action\n  redo() {\n    if (!this.canRedo) return;\n    const entry = this.redoStack[this.redoStack.length - 1];\n    this.undoStack = [\n      ...this.undoStack,\n      {\n        description: entry.description,\n        snapshot: JSON.parse(JSON.stringify(this.items)),\n      },\n    ];\n    this.redoStack = this.redoStack.slice(0, -1);\n    this.items = entry.snapshot;\n  }\n\n  @action\n  async duplicateItem(item) {\n    this._pushUndo('duplicate item');\n    const record = this.store.createRecord('todo', {\n      label: `${item.label} (copy)`,\n      done: false,\n      priority: item.priority,\n      category: item.category,\n      dueDate: item.dueDate,\n      notes: item.notes,\n      createdAt: new Date(),\n    });\n    await record.save();\n    this.items = [...this.items, record];\n  }\n\n  @action\n  exportItems() {\n    const data = this.items.map((item) => ({\n      label: item.label,\n      done: item.done,\n      priority: item.priority,\n      category: item.category,\n      dueDate: item.dueDate,\n      notes: item.notes,\n    }));\n    return JSON.stringify(data, null, 2);\n  }\n\n  @action\n  async importItems(jsonString) {\n    this._pushUndo('import items');\n    try {\n      const data = JSON.parse(jsonString);\n      for (const entry of data) {\n        const record = this.store.createRecord('todo', {\n          ...entry,\n          createdAt: new Date(),\n        });\n        await record.save();\n        this.items = [...this.items, record];\n      }\n      this.notify.success(`Imported ${data.length} items`);\n    } catch (error) {\n      this.notify.error('Invalid import data');\n    }\n  }\n}\n"
  },
  {
    "path": "tests/bench/small.gjs",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { on } from '@ember/modifier';\n\nexport default class Counter extends Component {\n  @tracked count = 0;\n\n  increment = () => {\n    this.count++;\n  };\n\n  <template>\n    <button type=\"button\" {{on \"click\" this.increment}}>\n      {{@label}}: {{this.count}}\n    </button>\n  </template>\n}\n"
  },
  {
    "path": "tests/bench/small.gts",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { on } from '@ember/modifier';\n\ninterface Args {\n  label: string;\n}\n\nexport default class Counter extends Component<{ Args: Args }> {\n  @tracked count = 0;\n\n  increment = () => {\n    this.count++;\n  };\n\n  <template>\n    <button type=\"button\" {{on \"click\" this.increment}}>\n      {{@label}}: {{this.count}}\n    </button>\n  </template>\n}\n"
  },
  {
    "path": "tests/bench/small.js",
    "content": "import Component from '@glimmer/component';\nimport { tracked } from '@glimmer/tracking';\nimport { action } from '@ember/object';\nimport { inject as service } from '@ember/service';\n\nexport default class CounterComponent extends Component {\n  @service router;\n\n  @tracked count = 0;\n\n  @action\n  increment() {\n    this.count++;\n  }\n\n  get isPositive() {\n    return this.count > 0;\n  }\n}\n"
  },
  {
    "path": "tests/config-setup.js",
    "content": "'use strict';\n\nconst { readdirSync, readFileSync } = require('fs');\nconst path = require('path');\nconst configs = require('../lib').configs;\n\nconst CONFIG_NAMES = Object.keys(configs);\n\ndescribe('config setup is correct', function () {\n  it('should have a list of exported configs and config directory that match', function () {\n    const filePath = path.join(__dirname, '..', 'lib', 'config');\n    const files = readdirSync(filePath);\n\n    expect(CONFIG_NAMES).toEqual(\n      files.filter((file) => !file.startsWith('.')).map((file) => file.replace('.js', ''))\n    );\n  });\n\n  it('should mention all configs in the README', function () {\n    const filePath = path.join(__dirname, '..', 'README.md');\n    const file = readFileSync(filePath, 'utf8');\n\n    for (const configName of CONFIG_NAMES) {\n      expect(file).toContain(configName);\n    }\n  });\n});\n"
  },
  {
    "path": "tests/fixtures/template-no-this-in-template-only-components/app/components/classic-with-class.js",
    "content": "// Fixture for template-no-this-in-template-only-components rule tests.\n// Only needs to exist on disk; the rule's filesystem check uses fs.existsSync.\nmodule.exports = {};\n"
  },
  {
    "path": "tests/fixtures/template-no-this-in-template-only-components/app/components/with-class.js",
    "content": "// Fixture for template-no-this-in-template-only-components rule tests.\n// Only needs to exist on disk; the rule's filesystem check uses fs.existsSync.\nmodule.exports = {};\n"
  },
  {
    "path": "tests/fixtures/template-no-this-in-template-only-components/app/components/with-ts-class.ts",
    "content": "// Fixture for template-no-this-in-template-only-components rule tests.\n// Only needs to exist on disk; the rule's filesystem check uses fs.existsSync.\nexport {};\n"
  },
  {
    "path": "tests/helpers/babel-eslint-parser.js",
    "content": "const babelESLint = require('@babel/eslint-parser');\n\nfunction parse(code) {\n  return babelESLint.parse(code, {\n    babelOptions: {\n      configFile: require.resolve('../../.babelrc'),\n    },\n  });\n}\n\nfunction parseForESLint(code) {\n  return babelESLint.parseForESLint(code, {\n    babelOptions: {\n      configFile: require.resolve('../../.babelrc'),\n    },\n  });\n}\n\nmodule.exports = {\n  parse,\n  parseForESLint,\n};\n"
  },
  {
    "path": "tests/helpers/faux-context.js",
    "content": "'use strict';\n\nconst { parseForESLint } = require('../helpers/babel-eslint-parser');\nconst { SourceCode } = require('eslint');\n/**\n * Builds a fake ESLint context object that's enough to satisfy the contract\n * expected by `getSourceModuleNameForIdentifier` and `isEmberCoreModule`\n */\nclass FauxContext {\n  constructor(code, filename = '', report = () => {}) {\n    const { ast } = parseForESLint(code);\n\n    this.ast = ast;\n    this.filename = filename;\n    this.report = report;\n    this.code = code;\n    this.sourceCode = new SourceCode(this.code, this.ast);\n  }\n\n  /**\n   * Does not build the full tree of \"ancestors\" for the identifier, but\n   * we only care about the first one; the Program node\n   */\n  getAncestors() {\n    return [this.ast];\n  }\n}\n\nmodule.exports = {\n  FauxContext,\n};\n"
  },
  {
    "path": "tests/helpers/test-case.js",
    "content": "module.exports = {\n  addPrefix,\n  addComputedImport,\n};\n\nfunction addPrefix(testCase, prefix) {\n  if (typeof testCase === 'string') {\n    return `${prefix}${testCase}`;\n  }\n\n  const updated = {\n    ...testCase,\n    code: `${prefix}${testCase.code}`,\n  };\n\n  if (testCase.output) {\n    updated.output = `${prefix}${testCase.output}`;\n  }\n\n  return updated;\n}\n\nfunction addComputedImport(testCase) {\n  const importStatement = \"import { computed } from '@ember/object';\\n\";\n  return addPrefix(testCase, importStatement);\n}\n"
  },
  {
    "path": "tests/lib/eslint-directive-comments-test.js",
    "content": "'use strict';\n\n/**\n * Regression coverage for `{{! eslint-disable-* }}` directives inside\n * `<template>` blocks in .gjs/.gts files.\n *\n * ESLint's inline-config scanner reads `Program.comments`. For .gjs/.gts the\n * template is parsed by ember-eslint-parser (via ember-estree). Upstream\n * ember-estree 0.4.3 (NullVoxPopuli/ember-estree#31) kept Glimmer comment\n * nodes inside the template body and stopped mirroring them into\n * `Program.comments`, which silently dropped template-level directives.\n *\n * The current lockfile pins ember-estree@0.4.2 so these tests pass today.\n * They exist to trip loudly if a future bump re-introduces the regression\n * without a corresponding fix in ember-eslint-parser / ember-estree.\n * The pure-hbs parser path has always behaved correctly and is exercised\n * here as a positive control.\n */\n\nconst { Linter } = require('eslint');\nconst rule = require('../../lib/rules/template-no-unnecessary-concat');\n\nconst RULE_ID = 'ember/template-no-unnecessary-concat';\n\nfunction makeLinter(parserName) {\n  const linter = new Linter();\n  linter.defineParser('ember-eslint-parser', require('ember-eslint-parser'));\n  linter.defineParser('ember-eslint-parser/hbs', require('ember-eslint-parser/hbs'));\n  linter.defineRule(RULE_ID, rule);\n  return {\n    verify(code, filename) {\n      return linter.verify(\n        code,\n        {\n          parser: parserName,\n          parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n          rules: { [RULE_ID]: 'error' },\n        },\n        { filename }\n      );\n    },\n  };\n}\n\ndescribe('eslint-disable directives inside <template> — .gts (gjs-gts-parser)', () => {\n  const linter = makeLinter('ember-eslint-parser');\n\n  it('rule fires without a directive (sanity check)', () => {\n    const code = ['<template>', '  <li aria-current=\"{{foo}}\"></li>', '</template>'].join('\\n');\n    const messages = linter.verify(code, 'test.gts');\n    const ruleMsgs = messages.filter((m) => m.ruleId === RULE_ID);\n    expect(ruleMsgs).toHaveLength(1);\n  });\n\n  it('{{! eslint-disable-next-line ember/rule }} suppresses the rule on the next line', () => {\n    const code = [\n      '<template>',\n      '  <li',\n      '    {{! eslint-disable-next-line ember/template-no-unnecessary-concat }}',\n      '    aria-current=\"{{foo}}\"',\n      '  ></li>',\n      '</template>',\n    ].join('\\n');\n    const messages = linter.verify(code, 'test.gts');\n    const ruleMsgs = messages.filter((m) => m.ruleId === RULE_ID);\n    expect(ruleMsgs).toEqual([]);\n  });\n\n  it('long-form {{!-- eslint-disable-next-line ember/rule --}} suppresses the rule', () => {\n    const code = [\n      '<template>',\n      '  <li',\n      '    {{!-- eslint-disable-next-line ember/template-no-unnecessary-concat --}}',\n      '    aria-current=\"{{foo}}\"',\n      '  ></li>',\n      '</template>',\n    ].join('\\n');\n    const messages = linter.verify(code, 'test.gts');\n    const ruleMsgs = messages.filter((m) => m.ruleId === RULE_ID);\n    expect(ruleMsgs).toEqual([]);\n  });\n});\n\ndescribe('eslint-disable directives inside <template> — .hbs (hbs-parser) [positive control]', () => {\n  const linter = makeLinter('ember-eslint-parser/hbs');\n\n  it('rule fires without a directive (sanity check)', () => {\n    const code = '<li aria-current=\"{{foo}}\"></li>\\n';\n    const messages = linter.verify(code, 'test.hbs');\n    const ruleMsgs = messages.filter((m) => m.ruleId === RULE_ID);\n    expect(ruleMsgs).toHaveLength(1);\n  });\n\n  it('{{! eslint-disable-next-line ember/rule }} suppresses the rule on the next line', () => {\n    const code = [\n      '<li',\n      '  {{! eslint-disable-next-line ember/template-no-unnecessary-concat }}',\n      '  aria-current=\"{{foo}}\"',\n      '></li>',\n    ].join('\\n');\n    const messages = linter.verify(code, 'test.hbs');\n    const ruleMsgs = messages.filter((m) => m.ruleId === RULE_ID);\n    expect(ruleMsgs).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/alias-model-in-controller.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/alias-model-in-controller');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('alias-model-in-controller', rule, {\n  valid: [\n    // direct alias\n\n    'export default Ember.Controller.extend({nail: alias(\"model\")});',\n    'export default Ember.Controller.extend({nail: computed.alias(\"model\")});',\n    'export default Ember.Controller.extend({nail: Ember.computed.alias(\"model\")});',\n    'export default Ember.Controller.extend({nail: readOnly(\"model\")});',\n    'export default Ember.Controller.extend({nail: computed.readOnly(\"model\")});',\n    'export default Ember.Controller.extend({nail: Ember.computed.readOnly(\"model\")});',\n    'export default Ember.Controller.extend({nail: reads(\"model\")});',\n    'export default Ember.Controller.extend({nail: computed.reads(\"model\")});',\n    'export default Ember.Controller.extend({nail: Ember.computed.reads(\"model\")});',\n    'export default Ember.Controller.extend(TestMixin, {nail: Ember.computed.alias(\"model\")});',\n    'const body = {nail: Ember.computed.alias(\"model\")}; export default Ember.Controller.extend(TestMixin, body);', // With object variable.\n    'export default Ember.Controller.extend(TestMixin, TestMixin2, {nail: Ember.computed.alias(\"model\")});',\n    {\n      filename: 'example-app/controllers/path/to/some-feature.js',\n      code: 'export default CustomController.extend({nail: alias(\"model\")});',\n    },\n\n    // nested alias\n\n    'export default Ember.Controller.extend({nail: alias(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: computed.alias(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: Ember.computed.alias(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: readOnly(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: computed.readOnly(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: Ember.computed.readOnly(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: reads(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: computed.reads(\"model.nail\")});',\n    'export default Ember.Controller.extend({nail: Ember.computed.reads(\"model.nail\")});',\n    'export default Ember.Controller.extend(TestMixin, {nail: Ember.computed.alias(\"model.nail\")});',\n    'export default Ember.Controller.extend(TestMixin, TestMixin2, {nail: Ember.computed.alias(\"model.nail\")});',\n    {\n      filename: 'example-app/controllers/path/to/some-feature.js',\n      code: 'export default CustomController.extend({nail: alias(\"model.nail\")});',\n    },\n  ],\n  invalid: [\n    {\n      code: 'export default Ember.Controller.extend({});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Controller.extend({resetPassword: service()});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Controller.extend({resetPassword: inject.service()});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Controller.extend({resetPassword: Ember.inject.service()});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Controller.extend(TestMixin, {});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Controller.extend(TestMixin, TestMixin2, {});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/controllers/path/to/some-feature.js',\n      code: 'export default CustomController.extend({});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/some-feature/controller.js',\n      code: 'export default CustomController.extend({});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/twisted-path/some-file.js',\n      code: 'export default Controller.extend({});',\n      output: null,\n      errors: [\n        {\n          message: 'Alias your model',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/avoid-leaking-state-in-ember-objects.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/avoid-leaking-state-in-ember-objects');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\neslintTester.run('avoid-leaking-state-in-ember-objects', rule, {\n  valid: [\n    'export default Foo.extend();',\n    'export default Foo.extend({ someProp: \"example\", init() { this.set(\"anotherProp\", []) } });',\n    'export default Foo.extend({ someProp: \"example\", init() { this.set(\"anotherProp\", {}) } });',\n    'export default Foo.extend({ someProp: \"example\", init() { this.set(\"anotherProp\", new Ember.A()) } });',\n    'export default Foo.extend({ someProp: \"example\", init() { this.set(\"anotherProp\", new A()) } });',\n    'export default Foo.extend({ someProp: \"example\", init() { this.set(\"anotherProp\", new Ember.Object()) } });',\n    'export default Foo.extend({ someProp: \"example\", init() { this.set(\"anotherProp\", new Object()) } });',\n    'export default Foo.extend({ classNames: [], classNameBindings: [], actions: {}, concatenatedProperties: [], mergedProperties: [], positionalParams: [] });',\n    'export default Foo.extend(someMixin, { classNames: [], classNameBindings: [], actions: {}, concatenatedProperties: [], mergedProperties: [], positionalParams: [] });',\n    'export default Foo.extend({ someProp: \"example\",});',\n    'export default Foo.extend({ someProp: function() {},});',\n    'export default Foo.extend({ someProp: 5,});',\n    'export default Foo.extend({ someProp: Symbol(),});',\n    'export default Foo.extend({ someProp: undefined,});',\n    'export default Foo.extend({ someProp: null,});',\n    'export default Foo.extend({ doStuff() { }});',\n    'export default Foo.extend({ derp: importedThing });',\n    'export default Foo.extend({ derp });',\n    'export default Foo.extend(SomeMixin, { simple: \"string\" });',\n    'export default Foo.extend(SomeMixin, OtherMixin, { derp: null });',\n    'export default Foo.extend({ doStuff: task(function* () {}) });',\n    'export default Foo.extend({ fullName: computed(function() {}) });',\n    'export default Foo.extend({ fullName: inject.service() });',\n    \"export default Foo.extend({ fullName: 'a' + 'b' });\",\n    'export default Foo.extend({ test: hbs`lorem ipsum` });',\n    'export default Foo.extend({ test: `lorem ipsum` });',\n    'export default Foo.extend({ fullName: abc.dgc });',\n    'export default Foo.extend({ foo: abc.something() });',\n    'export default Foo.extend({ foo: !true });',\n    'export default Foo.extend({ ...props });',\n    'export default Foo.extend({ foo: condition ? \"foo\" : \"bar\" });',\n    'export default Foo.extend({ foo: \"foo\" && \"bar\" });',\n    'export default Foo.extend({ foo: \"foo\" || \"bar\" });',\n    'import Mixin from \"@ember/object/mixin\"; export default Mixin.create({});',\n    'import Mixin from \"@ember/object/mixin\"; export default Mixin.create({ harmlessProp: \"foo\" });',\n    'import Mixin from \"@ember/object/mixin\"; export default Mixin.create(NestedMixin, {});',\n    'import Mixin from \"@ember/object/mixin\";',\n    'import Component from \"@ember/component\"; export default class MyNativeClassComponent extends Component { someArrayField = []; }',\n    'import Component from \"@ember/component\"; export default class MyNativeClassComponent extends Component { someObjectField = {}; }',\n    'import EmberObject from \"@ember/object\"; export default class MyNativeClassComponentWithAMixin extends EmberObject.extend(MyMixin) { someArrayField = []; }',\n    { code: 'export default Foo.extend({ someProp: [] });', options: [['someProp']] }, // With options.\n    { code: 'export default Foo.extend({ someProp: [], actions: {} });', options: [['someProp']] }, // With options and known Ember property.\n\n    // TypeScript type assertions should be unwrapped\n    {\n      code: 'export default Foo.extend({ someProp: undefined as string | undefined });',\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n    {\n      code: 'export default Foo.extend({ someProp: inject() as unknown as Store });',\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n  ],\n  invalid: [\n    {\n      code: 'export default Foo.extend({someProp: []});',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      // With options.\n      code: 'export default Foo.extend({someProp: [], someProp2: [], actions: {} });',\n      output: null,\n      options: [['someProp']],\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      // With object variable.\n      code: 'const body = {someProp: []}; export default Foo.extend(body);',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({someProp: new Ember.A()});',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({someProp: new A()});',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({someProp: {}});',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({someProp: new Ember.Object()});',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({someProp: new Object()});',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n\n    {\n      code: 'export default Foo.extend(SomeMixin, { derp: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ badThing: new Set() });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: \"export default Foo['extend']({ otherThing: {} });\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.reopen({ otherThing: {} });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ foo: condition ? {} : false });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ foo: condition ? false : {} });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ foo: false && {} });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ foo: {} && false });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ foo: false || {} });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'export default Foo.extend({ foo: {} || false });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'import Mixin from \"@ember/object/mixin\"; export default Mixin.create({ anArray: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n    {\n      code: 'import Ember from \"ember\"; export default Ember.Mixin.create({ anObject: {} });',\n      output: null,\n      errors: [\n        {\n          message:\n            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/avoid-using-needs-in-controllers.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/avoid-using-needs-in-controllers');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\neslintTester.run('avoid-using-needs-in-controllers', rule, {\n  valid: [\n    'export default Controller.extend();',\n    'export default Controller.extend({ ...foo });',\n    'export default Controller.extend({ random: [] });',\n    'export default FooController.extend();',\n    'Controller.reopen();',\n    'FooController.reopen();',\n    'Controller.reopenClass();',\n    'FooController.reopenClass();',\n  ],\n  invalid: [\n    {\n      code: 'export default Controller.extend({ needs: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      // With object variable.\n      code: 'const body = { needs: [] }; export default Controller.extend(body);',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      code: 'Controller.reopenClass({ needs: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      code: 'Controller.reopen({ needs: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      code: \"export default Controller['extend']({ needs: [] });\",\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/controllers/some-controller.js',\n      code: 'export default FooController.extend({ needs: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/controllers/some-controller.js',\n      code: 'FooController.reopenClass({ needs: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/controllers/some-controller.js',\n      code: 'FooController.reopen({ needs: [] });',\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/controllers/some-controller.js',\n      code: \"export default FooController['extend']({ needs: [] });\",\n      output: null,\n      errors: [\n        {\n          message:\n            '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/classic-decorator-hooks.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/classic-decorator-hooks');\nconst RuleTester = require('eslint').RuleTester;\n\nconst {\n  ERROR_MESSAGE_INIT_IN_NON_CLASSIC,\n  ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC,\n  ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC,\n} = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('classic-decorator-hooks', rule, {\n  valid: [\n    `\n      const Foo = EmberObject.extend({\n        init() {},\n        destroy() {},\n        ...foo\n      })\n    `,\n    `\n      class Foo extends Bar {\n        constructor() {}\n        willDestroy() {}\n      }\n    `,\n    `\n      @classic\n      class Foo extends Bar {\n        init() {}\n        destroy() {}\n      }\n    `,\n    `\n      class Foo {\n        init() {}\n        destroy() {}\n      }\n    `,\n  ],\n\n  invalid: [\n    {\n      code: `\n        class Foo extends Bar {\n          init() {}\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_INIT_IN_NON_CLASSIC,\n        },\n      ],\n    },\n    {\n      code: `\n        class Foo extends Bar {\n          destroy() {}\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC,\n        },\n      ],\n    },\n    {\n      code: `\n        @classic\n        class Foo extends Bar {\n          constructor() {}\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/classic-decorator-no-classic-methods.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/classic-decorator-no-classic-methods');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { disallowedMethodErrorMessage } = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('classic-decorator-no-classic-methods', rule, {\n  valid: [\n    `\n      const Foo = EmberObject.extend({\n        foo() {\n          this.get('bar');\n        }\n      })\n    `,\n    `\n      class Foo extends Bar {\n        foo() {\n          get(this, 'bar');\n          this.baz();\n        }\n      }\n    `,\n    `\n      @classic\n      class Foo extends Bar {\n        foo() {\n          this.get('bar');\n        }\n      }\n    `,\n    `\n      class Foo {\n        foo() {\n          this.get('bar');\n        }\n      }\n    `,\n    `\n      class Foo extends Bar {\n        foo = otherClass.get('bar');\n      }\n    `,\n    `\n      class Foo extends Bar {\n        #get = (k) => {};\n        foo = () => {\n          this.#get('abc');\n        }\n      }\n    `,\n  ],\n\n  invalid: [\n    {\n      code: `\n        class Foo extends Bar {\n          foo() {\n            this.get('bar');\n          }\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: disallowedMethodErrorMessage('get'),\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        class Foo extends Bar {\n          foo = this.get('bar');\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: disallowedMethodErrorMessage('get'),\n          type: 'MemberExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/closure-actions.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/closure-actions');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('closure-actions', rule, {\n  valid: [\n    'export default Component.extend();',\n    'export default Component.extend({actions: {pushLever() {this.attr.boom();}}});',\n  ],\n  invalid: [\n    {\n      code: 'export default Component.extend({actions: {pushLever() {this.sendAction(\"detonate\");}}});',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/computed-property-getters.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/computed-property-getters');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst { PREVENT_GETTER_MESSAGE, ALWAYS_GETTER_MESSAGE, ALWAYS_WITH_SETTER_MESSAGE } = rule;\nconst ruleTester = new RuleTester();\nconst parserOptions = { ecmaVersion: 2022, sourceType: 'module' };\nconst output = null;\n\nconst alwaysWithSetterOptionErrors = [\n  {\n    message: ALWAYS_WITH_SETTER_MESSAGE,\n  },\n];\n\nconst neverOptionErrors = [\n  {\n    message: PREVENT_GETTER_MESSAGE,\n  },\n];\n\nconst alwaysOptionErrors = [\n  {\n    message: ALWAYS_GETTER_MESSAGE,\n  },\n];\n\nconst codeWithRawComputed = [\n  `\n  {\n    foo: computed('model')\n  }`,\n  `\n  {\n    foo: computed()\n  }`,\n  `\n  import Ember from 'ember';\n  {\n    foo: Ember.computed()\n  }`,\n  `\n  {\n    foo: computed\n  }`,\n];\n\nconst codeWithoutGettersOrSetters = [\n  `\n  {\n    foo: computed('model', function() {})\n  }`,\n  `{\n      foo: computed('model', function() {}).volatile()\n    }`,\n  `{\n      foo: computed(function() {})\n    }`,\n  `{\n      foo: computed({ random() {} })\n    }`,\n  `{\n      foo: computed(async function() {})\n    }`,\n  `{\n      foo: computed('model', async function() {})\n    }`,\n];\n\nconst codeWithOnlyGetters = [\n  `{\n      foo: computed({\n        get() {\n          return true;\n        }\n      }).readonly()\n    }`,\n  `{\n      foo: computed({\n        get() {\n          return true;\n        }\n      })\n    }`,\n  `{\n      foo: computed('model.foo', {\n        get() {\n          return true;\n        }\n      }).readonly()\n    }`,\n  `{\n      foo: computed('model.foo', {\n        get() {\n          return true;\n        }\n      })\n    }`,\n  `{\n      foo: computed('model.foo', {\n        get() {}\n      })\n    }`,\n  `import Ember from 'ember';\n   {\n      foo: Ember.computed('model.foo', {\n        get() {}\n      })\n    }`,\n];\n\nconst codeWithOnlySetters = [\n  `{\n      foo: computed({\n        set() {\n          return true;\n        }\n      }).readonly()\n    }`,\n  `{\n      foo: computed({\n        set() {\n          return true;\n        }\n      })\n    }`,\n  `{\n      foo: computed('model.foo', {\n        set() {\n          return true;\n        }\n      }).readonly()\n    }`,\n  `{\n      foo: computed('model.foo', {\n        set() {\n          return true;\n        }\n      })\n    }`,\n  `{\n      foo: computed('model.foo', {\n        set() {}\n      })\n    }`,\n];\n\nconst codeWithSettersAndGetters = [\n  `{\n      foo: computed({\n        get() {\n          return true;\n        },\n        set() {\n          return true;\n        }\n      }).readonly()\n    }`,\n  `{\n      foo: computed({\n        get() {\n          return true;\n        },\n        set() {\n          return true;\n        }\n      })\n    }`,\n  `{\n      foo: computed('model.foo', {\n        get() {\n          return true;\n        },\n        set() {\n          return true;\n        }\n      }).readonly()\n    }`,\n  `{\n      foo: computed('model.foo', {\n        get() {\n          return true;\n        },\n        set() {\n          return true;\n        }\n      })\n    }`,\n  `{\n      foo: computed('model.foo', {\n        get() {},\n        set() {},\n      })\n    }`,\n];\n\nconst validWithDefaultOptions = [\n  ...codeWithoutGettersOrSetters.map((code) => ({ code, parserOptions })),\n  ...codeWithSettersAndGetters.map((code) => ({ code, parserOptions })),\n  ...codeWithRawComputed.map((code) => ({ code, parserOptions })),\n];\n\nconst validWithAlwaysWithSetterOptions = [\n  ...codeWithoutGettersOrSetters.map((code) => {\n    const options = ['always-with-setter'];\n    return { code, parserOptions, options };\n  }),\n  ...codeWithSettersAndGetters.map((code) => {\n    const options = ['always-with-setter'];\n    return { code, parserOptions, options };\n  }),\n  ...codeWithRawComputed.map((code) => {\n    const options = ['always-with-setter'];\n    return { code, parserOptions, options };\n  }),\n];\n\nconst validWithNeverOption = [\n  ...codeWithoutGettersOrSetters.map((code) => {\n    const options = ['never'];\n    return { code, parserOptions, options };\n  }),\n  ...codeWithRawComputed.map((code) => {\n    const options = ['never'];\n    return { code, parserOptions, options };\n  }),\n];\n\nconst validWithAlwaysOption = [\n  ...codeWithOnlyGetters.map((code) => {\n    const options = ['always'];\n    return { code, parserOptions, options };\n  }),\n  ...codeWithSettersAndGetters.map((code) => {\n    const options = ['always'];\n    return { code, parserOptions, options };\n  }),\n  ...codeWithRawComputed.map((code) => {\n    const options = ['always'];\n    return { code, parserOptions, options };\n  }),\n];\n\nconst inValidWithDefaultOptions = [\n  ...codeWithOnlyGetters.map((code) => ({\n    code,\n    parserOptions,\n    output,\n    errors: alwaysWithSetterOptionErrors,\n  })),\n  ...codeWithOnlySetters.map((code) => ({\n    code,\n    parserOptions,\n    output,\n    errors: alwaysWithSetterOptionErrors,\n  })),\n];\n\nconst inValidWithNeverOption = [\n  ...codeWithOnlyGetters.map((code) => {\n    const options = ['never'];\n    return { code, parserOptions, options, output, errors: neverOptionErrors };\n  }),\n  ...codeWithOnlySetters.map((code) => {\n    const options = ['never'];\n    return { code, parserOptions, options, output, errors: neverOptionErrors };\n  }),\n  ...codeWithSettersAndGetters.map((code) => {\n    const options = ['never'];\n    return { code, parserOptions, options, output, errors: neverOptionErrors };\n  }),\n];\n\nconst inValidWithAlwaysOption = [\n  ...codeWithoutGettersOrSetters.map((code) => {\n    const options = ['always'];\n    return { code, parserOptions, options, output, errors: alwaysOptionErrors };\n  }),\n  ...codeWithOnlySetters.map((code) => {\n    const options = ['always'];\n    return { code, parserOptions, options, output, errors: alwaysOptionErrors };\n  }),\n];\n\nruleTester.run('computed-property-getters', rule, {\n  valid: [\n    ...validWithDefaultOptions,\n    ...validWithAlwaysWithSetterOptions,\n    ...validWithNeverOption,\n    ...validWithAlwaysOption,\n  ].map(addComputedImport),\n  invalid: [\n    ...inValidWithDefaultOptions,\n    ...inValidWithNeverOption,\n    ...inValidWithAlwaysOption,\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/jquery-ember-run.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/jquery-ember-run');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE } = rule;\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('jquery-ember-run', rule, {\n  valid: [\n    // jQuery/bind from Ember\n    `import Ember from \"ember\";\n     Ember.$(\"#item\").on(\"click\", () => { Ember.run.bind(this, this.handle); });`,\n    `import Ember from \"ember\";\n     Ember.$(\"#item\").on(\"click\", () => { Ember.run.debounce(this, this.handle); });`,\n\n    // jQuery from Ember with destructuring\n    `import Ember from \"ember\";\n     const { $ } = Ember;\n     $(\"#item\").on(\"click\", () => { Ember.run.bind(this, this.handle); });`,\n\n    // Global jQuery\n    {\n      code: `\n      import { bind } from \"@ember/runloop\";\n      $(\"#item\").on(\"click\", () => { bind(this, this.handle); });`,\n      globals: { $: true },\n    },\n\n    // Imported jQuery\n    `import { bind } from \"@ember/runloop\";\n     import $ from \"jquery\";\n     $(\"#item\").on(\"click\", () => { bind(this, this.handle); });`,\n    `import { debounce } from \"@ember/runloop\";\n     import $ from \"jquery\";\n     $(\"#item\").on(\"click\", () => { debounce(this, this.handle); });`,\n\n    // No callback\n    'import $ from \"jquery\"; $(\"#item\");',\n    'import $ from \"jquery\"; $(\"#item\").on(\"click\");',\n    'import $ from \"jquery\"; $(\"#item\").on(\"click\", function() {});',\n    'import $ from \"jquery\"; $(\"#item\").on(\"click\", () => {});',\n\n    // Callback but not jQuery\n    'notJquery(\"#item\").on(\"click\", () => {this.handle();});',\n\n    // Not `on`\n    'import $ from \"jquery\"; $(\"#item\").notOn(\"click\", () => {this.handle();});',\n  ],\n  invalid: [\n    {\n      // jQuery from Ember\n      code: 'import Ember from \"ember\"; Ember.$(\"#item\").on(\"click\", () => { this.handle(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      // jQuery from Ember with destructuring\n      code: 'import Ember from \"ember\"; const { $ } = Ember; $(\"#item\").on(\"click\", () => { this.handle(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      // Global jQuery\n      code: '$(\"#item\").on(\"click\", () => {this.handle();});',\n      output: null,\n      globals: { $: true },\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      // Imported jQuery\n      code: 'import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { this.handle(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      // With function call that isn't a runloop function\n      code: 'import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { unknownFunction(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n    {\n      // With unknown imported runloop function\n      code: 'import { unknownFunction } from \"@ember/runloop\"; import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { unknownFunction(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n    {\n      // With assignment\n      code: 'import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { this.value = 1; });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // With not from Ember\n      code: 'import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { notEmber.run.bind(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      // With not from Ember.run\n      code: 'import Ember from \"ember\"; import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { Ember.notRun.bind(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      // With unknown function call from Ember.run\n      code: 'import Ember from \"ember\"; import $ from \"jquery\"; $(\"#item\").on(\"click\", () => { Ember.run.unknownFunction(); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/named-functions-in-promises.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/named-functions-in-promises');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022 },\n});\n\neslintTester.run('named-functions-in-promises', rule, {\n  valid: [\n    'user.save().then(this._reloadUser.bind(this));',\n    'user.save().catch(this._handleError.bind(this));',\n    'user.save().finally(this._finallyDo.bind(this));',\n    'user.save().then(this._reloadUser);',\n    'user.save().catch(this._handleError);',\n    'user.save().finally(this._finallyDo);',\n    'user.save().then(_reloadUser);',\n    'user.save().catch(_handleError);',\n    'user.save().finally(_finallyDo);',\n    {\n      code: 'user.save().then(() => this._reloadUser(user));',\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n    },\n    {\n      code: 'user.save().catch(err => this._handleError(err));',\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n    },\n    {\n      code: 'user.save().finally(() => this._finallyDo());',\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n    },\n    {\n      code: 'user.save().then(() => user.reload());',\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n    },\n  ],\n  invalid: [\n    {\n      code: 'user.save().then(() => {return user.reload();});',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().catch(() => {return error.handle();});',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().finally(() => {return finallyDo();});',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().then(() => {return user.reload();});',\n      output: null,\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().catch(() => {return error.handle();});',\n      output: null,\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().finally(() => {return finallyDo();});',\n      output: null,\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().then(() => this._reloadUser(user));',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().catch(err => this._handleError(err));',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().finally(() => this._finallyDo());',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().then(() => user.reload());',\n      output: null,\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n    {\n      code: 'user.save().then(user => user.name);',\n      output: null,\n      options: [\n        {\n          allowSimpleArrowFunction: true,\n        },\n      ],\n      errors: [\n        {\n          message: 'Use named functions defined on objects to handle promises',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/new-module-imports.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/new-module-imports');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n});\neslintTester.run('new-module-imports', rule, {\n  valid: [\n    {\n      code: `import Ember from 'ember';\n\n        const { Handlebars: { Utils: { escapeExpression } } } = Ember\n      `,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    'Ember.Handlebars.Utils.escapeExpression(\"foo\");',\n    'Ember.onerror = function() {};',\n    'Ember.MODEL_FACTORY_INJECTIONS = true;',\n    'console.log(Ember.SOMETHING_NO_ONE_USES);',\n    'if (Ember.testing) {}',\n    {\n      code: `import Component from '@ember/component';\n\n        export default Component.extend({});\n      `,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: `\n        import Controller from '@ember/controller';\n        import { bool } from '@ember/object/computed';\n\n        export default Controller.extend({\n          isTrue: bool(''),\n          ...foo\n        });\n      `,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: `import LOL from 'who-knows-but-definitely-not-ember';\n\n        const { Controller } = LOL;\n      `,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: 'export const Ember = 1;',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: 'for (let i = 0; i < 10; i++) { }',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    'SomethingRandom.Ember.Service;',\n  ],\n  invalid: [\n    {\n      code: `import Ember from 'ember';\n\n        const { Object: EmberObject } = Ember;\n\n        export default Component.extend({});\n      `,\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            \"Use `import EmberObject from '@ember/object';` instead of using Ember destructuring\",\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import LOL from 'ember';\n\n        const { Controller } = LOL;\n      `,\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            \"Use `import Controller from '@ember/controller';` instead of using Ember destructuring\",\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import Ember from 'ember';\n\n        const { $, Controller } = Ember;\n\n        export default Controller.extend({});\n      `,\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message: \"Use `import $ from 'jquery';` instead of using Ember destructuring\",\n          line: 3,\n        },\n        {\n          message:\n            \"Use `import Controller from '@ember/controller';` instead of using Ember destructuring\",\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import Ember from 'ember';\n\n        const { Component, String: { htmlSafe } } = Ember;\n        const TEST = 'MY TEST';\n\n        export default Component.extend({});\n      `,\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            \"Use `import Component from '@ember/component';` instead of using Ember destructuring\",\n          line: 3,\n        },\n        {\n          message:\n            \"Use `import { htmlSafe } from '@ember/template';` instead of using Ember destructuring\",\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import Ember from 'ember';\n\n        const { inject: { controller, service } } = Ember;\n\n        export default Ember.Component.extend({\n          myService: service('my-service')\n        });\n      `,\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            \"Use `import { inject as controller } from '@ember/controller';` instead of using Ember destructuring\",\n          line: 3,\n        },\n        {\n          message:\n            \"Use `import { inject as service } from '@ember/service';` instead of using Ember destructuring\",\n          line: 3,\n        },\n        {\n          message:\n            \"Use `import Component from '@ember/component';` instead of using Ember.Component\",\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        import Component from '@ember/component';\n\n        const { computed: { alias, uniq } } = Ember;\n\n        export default Component.extend({});\n      `,\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            \"Use `import { alias } from '@ember/object/computed';` instead of using Ember destructuring\",\n          line: 5,\n        },\n        {\n          message:\n            \"Use `import { uniq } from '@ember/object/computed';` instead of using Ember destructuring\",\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Service;',\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message: \"Use `import Service from '@ember/service';` instead of using Ember.Service\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: 'export default Ember.Service.extend({});',\n      output: null,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message: \"Use `import Service from '@ember/service';` instead of using Ember.Service\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: 'Ember.computed();',\n      output: null,\n      errors: [\n        {\n          message:\n            \"Use `import { computed } from '@ember/object';` instead of using Ember.computed\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: 'Ember.computed.not();',\n      output: null,\n      errors: [\n        {\n          message:\n            \"Use `import { not } from '@ember/object/computed';` instead of using Ember.computed.not\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: \"Ember.inject.service('foo');\",\n      output: null,\n      errors: [\n        {\n          message:\n            \"Use `import { inject } from '@ember/service';` instead of using Ember.inject.service\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: 'var Router = Ember.Router.extend({});',\n      output: null,\n      errors: [\n        {\n          message:\n            \"Use `import EmberRouter from '@ember/routing/router';` instead of using Ember.Router\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: \"Ember.$('.foo')\",\n      output: null,\n      errors: [\n        {\n          message: \"Use `import $ from 'jquery';` instead of using Ember.$\",\n          line: 1,\n        },\n      ],\n    },\n    {\n      code: 'new Ember.RSVP.Promise();',\n      output: null,\n      errors: [\n        {\n          message: \"Use `import { Promise } from 'rsvp';` instead of using Ember.RSVP.Promise\",\n          line: 1,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-actions-hash.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-actions-hash');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('no-actions-hash', rule, {\n  valid: [\n    `\n      export default Component.extend({\n        foo: action(function() {})\n      });\n    `,\n    `\n      export default class MyComponent extends Component {\n        @action\n        foo() {}\n      }\n    `,\n    `\n      export default Controller.extend({\n        foo: action(function() {})\n      });\n    `,\n    `\n      export default class MyController extends Controller {\n        @action\n        foo() {}\n      }\n    `,\n    `\n      export default Route.extend({\n        foo: action(function() {})\n      });\n    `,\n    `\n      export default class MyRoute extends Route {\n        @action\n        foo() {}\n      }\n    `,\n    `\n      export default class MyLovelyClass extends LovelyClass {\n        actions = {\n          foo() {\n          }\n        }\n      }\n    `,\n    `\n      export default Service.extend({\n        actions: {\n        },\n      });\n    `,\n\n    // Spread syntax\n    'Route.extend({ ...foo });',\n    'Route.extend(Evented, { ...foo });',\n    'Route.extend(...foo);',\n\n    // TypeScript declare property (value is null)\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {\n          declare actions: SomeType;\n        }\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n    // TypeScript definite assignment (value is also null)\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {\n          actions!: SomeType;\n        }\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n  ],\n\n  invalid: [\n    {\n      code: `\n        export default Component.extend({\n          actions: {\n          },\n        });\n      `,\n      output: null,\n      errors: [{ type: 'Property', message: ERROR_MESSAGE }],\n    },\n    {\n      // With object variable.\n      code: 'const body = { actions: { } }; export default Component.extend(body); ',\n      output: null,\n      errors: [{ type: 'Property', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {\n          actions = {\n            foo() {\n            }\n          }\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `\n        export default Controller.extend({\n          actions: {\n          },\n        });\n      `,\n      output: null,\n      errors: [{ type: 'Property', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default class MyController extends Controller {\n          actions = {\n            foo() {\n            }\n          }\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `\n        export default Route.extend({\n          actions: {\n          },\n        });\n      `,\n      output: null,\n      errors: [{ type: 'Property', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        export default class MyRoute extends Route {\n          actions = {\n            foo() {\n            }\n          }\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-array-prototype-extensions.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-array-prototype-extensions');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('no-array-prototype-extensions', rule, {\n  valid: [\n    '[1, 2, 4].filter(el => { /* ... */ })',\n    'something.filter(el => { /* ... */ })',\n    'something.find(item => item.isValid)',\n    'something.find(item => item.age === 18, {})',\n    \"filterBy(something, 'foo')\",\n    'this.filterBy()',\n    'something[0]',\n    'something[0].foo',\n    \"get(something, 'foo.0.bar')\",\n    \"get(something, 'foo.0')\",\n    \"get(something, 'foo')[0]\",\n    \"get(something, 'notfirstObject')\",\n    \"get(something, 'lastObjectSibling')\",\n    \"get(something, 'foo.lastObjectSibling.bar')\",\n    'firstObject',\n    'lastObject',\n    'something.length',\n    'something.firstObject()',\n    'something[something.length - 1]',\n    'something.isAny',\n    \"something['compact']\",\n    'replace()',\n    'replace(foo)',\n    'replace(foo, bar, baz)',\n    /** Optional chaining */\n    'arr?.notfirstObject?.foo',\n    'arr?.filter?.()',\n    /** String.prototype.replace() */\n    \"'something'.replace(regexp, 'substring')\",\n    \"something.replace(regexp, 'substring')\",\n\n    // Global non-array class (Promise.reject)\n    'window.Promise.reject();',\n    'Promise.reject();',\n    'Promise.reject(\"some reason\");',\n    'reject();',\n    'this.reject();',\n\n    // Promise.any()\n    'Promise.any();',\n    'window.Promise.any();',\n\n    // Global non-array class (RSVP.reject)\n    'RSVP.reject();',\n    'RSVP.reject(\"some reason\");',\n    'RSVP.Promise.reject();',\n    'Ember.RSVP.reject();',\n    'Ember.RSVP.Promise.reject();',\n\n    // `reject()` on instance of `RSVP.defer`.\n    `\n    import { defer } from 'rsvp';\n    const deferred = defer();\n    deferred.reject();`,\n    `\n    import { defer } from 'rsvp';\n    const requestDeferred = defer();\n    requestDeferred.reject();`,\n    `\n    import { defer } from 'rsvp';\n    const promise = defer();\n    promise.reject();`,\n    `\n    import { defer } from 'rsvp';\n    const fooPromise = defer();\n    fooPromise.reject();`,\n\n    // Global non-array class (*storage.clear)\n    'window.localStorage.clear();',\n    'window.sessionStorage.clear();',\n    'localStorage.clear();',\n    'sessionStorage.clear();',\n    'sessionStorage?.clear();',\n    'clear();',\n    'this.clear();',\n\n    // Global non-array class (location.replace)\n    'window.document.location.replace(url)',\n    'document.location.replace(url)',\n    'location.replace(url)',\n\n    // Lodash utility functions\n    \"lodash.compact([0, 1, false, 2, '', 3]);\",\n    \"_.compact([0, 1, false, 2, '', 3]);\",\n    '_.reject(users, function(o) { return !o.active; });',\n    \"_.toArray({ 'a': 1, 'b': 2 });\",\n    '_.uniq([2, 1, 2]);',\n    '_.uniqBy([2.1, 1.2, 2.3], Math.floor);',\n    \"_.replace('Hi Fred', 'Fred', 'Barney');\",\n\n    // jQuery\n    '$( \"li\" ).toArray();',\n    'jQuery( \"li\" ).toArray();',\n    'jquery( \"li\" ).toArray();',\n\n    // Non-array classes with clear() method\n    'const foo = new Set(); foo.clear();',\n    'const foo = new WeakSet(); foo.clear();',\n    'const foo = new Map(); foo.clear();',\n    'const foo = new WeakMap(); foo.clear();',\n    'const foo = new TrackedMap(); foo.clear();',\n    'const foo = new TrackedSet(); foo.clear();',\n    'const foo = new TrackedWeakMap(); foo.clear();',\n    'const foo = new TrackedWeakSet(); foo.clear();',\n\n    // Variable names with the Set/Map words and Set/Map function call.\n    'set.clear();',\n    'map.clear();',\n    'foo.set.clear();', // Longer chain.\n    // PascalCase\n    'Set.clear();',\n    'Map.clear();',\n    'SetFoo.clear();',\n    'SetMap.clear();',\n    'MySet.clear();',\n    'MyMap.clear();',\n    // camelCase\n    'aSetOfStuff.clear();',\n    'aMapOfStuff.clear();',\n    'setOfStuff.clear();',\n    'mapOfStuff.clear();',\n    // UPPER_CASE\n    'SET.clear();',\n    'MAP.clear();',\n    'SET_OF_STUFF.clear();',\n    'MAP_OF_STUFF.clear();',\n    'MY_SET.clear();',\n    'MY_MAP.clear();',\n    // snake_case\n    'a_set_of_stuff.clear();',\n    'a_map_of_stuff.clear();',\n    'set_foo.clear();',\n    'map_foo.clear();',\n\n    // super\n    'super.clear();',\n\n    // Class property definition with non-array class.\n    `class MyClass {\n      foo = new Set();\n      myFunc() {\n        this.foo.clear();\n      }\n    }`,\n\n    // Class property definition (private) with non-array class.\n    `class MyClass {\n      #foo = new Set();\n      myFunc() {\n        this.#foo.clear();\n      }\n    }`,\n\n    {\n      // Class property definition with non-array class (TypeScript).\n      code: `\n      class MyClass {\n        foo: Set<UploadFile> = new Set();\n        myFunc() {\n          this.foo.clear();\n        }\n      }\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n\n    {\n      // Class property definition (private) with non-array class (TypeScript).\n      code: `\n      class MyClass {\n        #foo: Set<UploadFile> = new TrackedSet();\n        myFunc() {\n          this.#foo.clear();\n        }\n      }\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n    {\n      // Class property definition (private) with non-array class (TypeScript) (does not confuse public/private properties).\n      code: `\n      class MyClass {\n        #foo: Set<UploadFile> = new TrackedSet();\n        foo: Array<UploadFile> = new Array();\n\n        myFunc() {\n          this.#foo.clear();\n        }\n      }\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n\n    // Methods called directly on EmberArray.\n    `\n      import { A } from '@ember/array'\n\n      const array = A([1, 2, 3])\n      array.toArray()\n    `,\n    `\n      import { A } from '@ember/array'\n\n      A([1, 2, 3]).toArray();\n    `,\n    `\n      import { A as SomeWeirdName } from '@ember/array'\n\n      const array = SomeWeirdName([1, 2, 3])\n      array.without(2)\n    `,\n\n    // Ember Data call with await.\n    `\n    class MyClass {\n      async _fetch(query) {\n        const response = await this.store.query('foo-bar', query);\n        return response.toArray();\n      }\n    }\n    `,\n    // Ember Data call without await.\n    `\n    class MyClass {\n      _fetch(query) {\n        const response = this.store.peekAll('foo-bar', query);\n        return response.toArray();\n      }\n    }\n    `,\n\n    // TODO: handle non-Identifier property names:\n    'foo[\"clear\"]();',\n\n    // Common ember-cli-mirage calls\n    'this.server.schema.users.findBy()',\n    'this.server.schema.devices.findBy()',\n    'this.server.schema.users.findBy({ foo: \"bar\" })',\n    'this.server.schema.devices.findBy({ foo: \"bar\" })',\n    'server.schema.users.findBy({ email: \"tony@stark.tech\" })',\n    'schema.users.findBy({ email: \"tony@stark.tech\" })',\n    'server.users.findBy({ email: \"tony@stark.tech\" })',\n    'server.db.users.findBy({ email: \"tony@stark.tech\" })',\n  ],\n  invalid: [\n    {\n      code: '[1, 2, 3].clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // map function call in chain\n      code: 'something.map().clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // set at beginning of chain but not directly before function call.\n      code: 'productSetMatcher.requiredItems.clear();',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // jquery at beginning of chain but not directly before function call.\n      code: 'jquery().foo.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // filterBy with two arguments\n      code: `\n      const arr = [];\n\n      function getAge() {\n        return 16;\n      }\n\n      arr.filterBy(\"age\", getAge());\n      `,\n      output: `\n      import { get } from '@ember/object';\nconst arr = [];\n\n      function getAge() {\n        return 16;\n      }\n\n      arr.filter(item => get(item, \"age\") === getAge());\n      `,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // filterBy with one argument\n      code: `\n      const arr = [];\n\n      arr.filterBy(\"age\");\n      `,\n      output: `\n      import { get } from '@ember/object';\nconst arr = [];\n\n      arr.filter(item => get(item, \"age\"));\n      `,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // filterBy with one argument and `get` import statement already imported\n      code: `\n      import { get as g } from '@ember/object';\n      const arr = [];\n\n      arr.filterBy(\"age\");\n      `,\n      output: `\n      import { get as g } from '@ember/object';\n      const arr = [];\n\n      arr.filter(item => g(item, \"age\"));\n      `,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // filterBy with one argument and `get` import statement imported from a different package\n      code: `\n      import { get as g } from 'dummy';\n      const arr = [];\n\n      arr.filterBy(\"age\");\n      `,\n      output: `\n      import { get } from '@ember/object';\nimport { get as g } from 'dummy';\n      const arr = [];\n\n      arr.filter(item => get(item, \"age\"));\n      `,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Set in variable name but not a Set function.\n      code: 'set.filterBy(\"age\", 18);',\n      output: `import { get } from '@ember/object';\nset.filter(item => get(item, \"age\") === 18);`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Set in variable name but not as its own word.\n      code: 'settle.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Map in variable name (front) but not as its own word.\n      code: 'mApple.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Map in variable name (middle) but not as its own word.\n      code: 'pumaParent.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Map in variable name (inside word) but not as its own word.\n      code: 'mapleTree.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.else.clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something?.firstObject?.foo',\n      output: null,\n      errors: [{ messageId: 'main', type: 'MemberExpression' }],\n    },\n    {\n      code: 'something?.clear?.()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'getSomething().clear()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.firstObject',\n      output: null,\n      errors: [{ messageId: 'main', type: 'MemberExpression' }],\n    },\n    {\n      code: \"get(something, 'foo.firstObject')\",\n      output: null,\n      errors: [{ messageId: 'main', type: 'Literal' }],\n    },\n    {\n      code: \"get(something, 'firstObject')\",\n      output: null,\n      errors: [{ messageId: 'main', type: 'Literal' }],\n    },\n    {\n      code: \"set(something, 'foo.firstObject.bar', 'something')\",\n      output: null,\n      errors: [{ messageId: 'main', type: 'Literal' }],\n    },\n\n    {\n      code: \"get(something, 'foo.lastObject')\",\n      output: null,\n      errors: [{ messageId: 'main', type: 'Literal' }],\n    },\n    {\n      code: \"set(something, 'lastObject', 'something')\",\n      output: null,\n      errors: [{ messageId: 'main', type: 'Literal' }],\n    },\n    {\n      code: \"get(something, 'foo.lastObject.bar')\",\n      output: null,\n      errors: [{ messageId: 'main', type: 'Literal' }],\n    },\n    {\n      code: 'something.compact()',\n      output: 'something.filter(item => item !== undefined && item !== null)',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of params are passed, skipping auto-fixing\n      code: 'something.compact(1, getVal(), 3)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.any()',\n      output: 'something.some()',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer doesn't get triggered\n      code: 'something.findBy()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer doesn't get triggered\n      code: 'something.findBy(1, 2, 3)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // findBy with single argument\n      code: \"something.findBy('abc')\",\n      output: `import { get } from '@ember/object';\nsomething.find(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // findBy with two arguments\n      code: \"something.findBy('abc', 'def')\",\n      output: `import { get } from '@ember/object';\nsomething.find(item => get(item, 'abc') === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // findBy with two arguments and @ember/object's `get` is already imported\n      code: `import { get } from '@ember/object';\n      something.findBy('abc', 'def')`,\n      output: `import { get } from '@ember/object';\n      something.find(item => get(item, 'abc') === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // findBy with two arguments and @ember/object's `get` is imported with an alias\n      code: `import { get as g } from '@ember/object';\n      something.findBy('abc', 'def')`,\n      output: `import { get as g } from '@ember/object';\n      something.find(item => g(item, 'abc') === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // findBy with two arguments and `get` is imported from package other than @ember/object\n      code: `import { get as g } from 'dummy';\n      something.findBy('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from 'dummy';\n      something.find(item => get(item, 'abc') === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.getEach()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.getEach(1, 2)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: \"something.getEach('abc')\",\n      output: `import { get } from '@ember/object';\nsomething.map(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.getEach(def)',\n      output: `import { get } from '@ember/object';\nsomething.map(item => get(item, def))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from `@ember/object` package\n      code: `import { get } from '@ember/object';\n      something.getEach('abc')`,\n      output: `import { get } from '@ember/object';\n      something.map(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported as alias from `@ember/object` package\n      code: `import { get as g } from '@ember/object';\n      something.getEach('abc')`,\n      output: `import { get as g } from '@ember/object';\n      something.map(item => g(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from a other than `@ember/object`\n      code: `import { get as g } from 'dummy';\n      something.getEach('abc')`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from 'dummy';\n      something.map(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.invoke()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.invoke(\"abc\")',\n      output: `import { get } from '@ember/object';\nsomething.map(item => get(item, \"abc\")?.())`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.invoke(\"abc\", \"def\")',\n      output: `import { get } from '@ember/object';\nsomething.map(item => get(item, \"abc\")?.(\"def\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from '@ember/object' package\n      code: `import { get } from '@ember/object';\n      something.invoke('abc', {})`,\n      output: `import { get } from '@ember/object';\n      something.map(item => get(item, 'abc')?.({}))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported with alias from '@ember/object' package\n      code: `import { get as g } from '@ember/object';\n      something.invoke('abc', {})`,\n      output: `import { get as g } from '@ember/object';\n      something.map(item => g(item, 'abc')?.({}))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from a package other than '@ember/object'\n      code: `import { get as g } from '@custom/object';\n      something.invoke('abc', {}, 'test', true)`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from '@custom/object';\n      something.map(item => get(item, 'abc')?.({}, 'test', true))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.isAny()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.isAny(\"abc\")',\n      output: `import { get } from '@ember/object';\nsomething.some(item => get(item, \"abc\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.isAny(\"abc\", \"def\")',\n      output: `import { get } from '@ember/object';\nsomething.some(item => get(item, \"abc\") === \"def\")`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from '@ember/object' package\n      code: `import { get } from '@ember/object';\n      something.isAny('abc', def)`,\n      output: `import { get } from '@ember/object';\n      something.some(item => get(item, 'abc') === def)`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported with alias from '@ember/object' package\n      code: `import { get as g } from '@ember/object';\n      something.isAny(abc, 'def')`,\n      output: `import { get as g } from '@ember/object';\n      something.some(item => g(item, abc) === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from a package other than '@ember/object'\n      code: `import { get as g } from '@custom/object';\n      something.isAny('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from '@custom/object';\n      something.some(item => get(item, 'abc') === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.isEvery()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.isEvery(\"abc\")',\n      output: `import { get } from '@ember/object';\nsomething.every(item => get(item, \"abc\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.isEvery(\"abc\", \"def\")',\n      output: `import { get } from '@ember/object';\nsomething.every(item => get(item, \"abc\") === \"def\")`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from '@ember/object' package\n      code: `import { get } from '@ember/object';\n      something.isEvery('abc', def)`,\n      output: `import { get } from '@ember/object';\n      something.every(item => get(item, 'abc') === def)`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported with alias from '@ember/object' package\n      code: `import { get as g } from '@ember/object';\n      something.isEvery(abc, 'def')`,\n      output: `import { get as g } from '@ember/object';\n      something.every(item => g(item, abc) === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` method is already imported from a package other than '@ember/object'\n      code: `import { get as g } from '@custom/object';\n      something.isEvery('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from '@custom/object';\n      something.every(item => get(item, 'abc') === 'def')`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.mapBy()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.mapBy(1, 2)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: \"something.mapBy('abc')\",\n      output: `import { get } from '@ember/object';\nsomething.map(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.mapBy(def)',\n      output: `import { get } from '@ember/object';\nsomething.map(item => get(item, def))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // @ember/object's `get` is already imported\n      code: `import { get } from '@ember/object';\n      something.mapBy('abc')`,\n      output: `import { get } from '@ember/object';\n      something.map(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // `get` is imported from package other than @ember/object\n      code: `import { get as g } from 'dummy';\n      something.mapBy('abc')`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from 'dummy';\n      something.map(item => get(item, 'abc'))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.objectAt()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.objectAt(1, 2)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.objectAt(1)',\n      output: 'something[1]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something?.objectAt(1)',\n      output: 'something?.[1]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Function call in chain\n      code: 'something.somethingElse.objectAt(1)',\n      output: 'something.somethingElse[1]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Function call in optional chaining\n      code: 'something.somethingElse?.objectAt(1)',\n      output: 'something.somethingElse?.[1]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.objectsAt()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.objectsAt(1, 2)',\n      output: '[1, 2].map((ind) => something[ind])',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.reject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Single argument is passed\n      code: 'something.reject(function(el) { return el.isFlagged; })',\n      output:\n        'something.filter(function(...args) { return !(function(el) { return el.isFlagged; }).apply(this, args); })',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Two arguments are passed\n      code: 'something.reject(function(el) { return el.isFlagged && this.isFlagged; }, {isFlagged: true})',\n      output:\n        'something.filter(function(...args) { return !(function(el) { return el.isFlagged && this.isFlagged; }).apply(this, args); }, {isFlagged: true})',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.rejectBy()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Single argument is passed\n      code: 'something.rejectBy(\"abc\")',\n      output: `import { get } from '@ember/object';\nsomething.filter(item => !get(item, \"abc\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Two arguments are passed\n      code: 'something.rejectBy(\"abc\", \"def\")',\n      output: `import { get } from '@ember/object';\nsomething.filter(item => get(item, \"abc\") !== \"def\")`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` is already imported from '@ember/object' package\n      code: `import { get } from '@ember/object';\n      something.rejectBy(\"abc\")`,\n      output: `import { get } from '@ember/object';\n      something.filter(item => !get(item, \"abc\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` is already imported with alias from '@ember/object' package\n      code: `import { get as g } from '@ember/object';\n      something.rejectBy(\"abc\")`,\n      output: `import { get as g } from '@ember/object';\n      something.filter(item => !g(item, \"abc\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When `get` is already imported from a package other than '@ember/object'\n      code: `import { get as g } from '@custom/object';\n      something.rejectBy(\"abc\")`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from '@custom/object';\n      something.filter(item => !get(item, \"abc\"))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.setEach()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.setEach(\"abc\")',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.setEach(\"abc\", 2)',\n      output: `import { set } from '@ember/object';\nsomething.forEach(item => set(item, \"abc\", 2))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When get method is already imported from @ember/object package.\n      code: `import { set } from '@ember/object';\n      something.setEach(\"abc\", 2)`,\n      output: `import { set } from '@ember/object';\n      something.forEach(item => set(item, \"abc\", 2))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When get method is already imported as alias from @ember/object package.\n      code: `import { set as s } from '@ember/object';\n      something.setEach(\"abc\", 2)`,\n      output: `import { set as s } from '@ember/object';\n      something.forEach(item => s(item, \"abc\", 2))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When get method is already imported from a package other than @ember/object.\n      code: `import { set as s } from '@custom/object';\n      something.setEach(\"abc\", 2)`,\n      output: `import { set } from '@ember/object';\nimport { set as s } from '@custom/object';\n      something.forEach(item => set(item, \"abc\", 2))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of arguments are passed, auto-fixer will not run\n      code: 'something.sortBy()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Arguments containing strings\n      code: \"something.sortBy('abc', 'def')\",\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\n[...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Single call expression argument.\n      code: 'something.sortBy(getKey())',\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\n[...something].sort((a, b) => {\n              const key = getKey();\n              return compare(get(a, key), get(b, key));\n            })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Single non CallExpression argument.\n      code: \"something.sortBy('abc')\",\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\n[...something].sort((a, b) => compare(get(a, 'abc'), get(b, 'abc')))`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Spread element as argument\n      code: 'something.sortBy(...abc)',\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\n[...something].sort((a, b) => {\n            for (const key of [...abc]) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Arguments other than strings\n      code: \"something.sortBy('abc', def)\",\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\n[...something].sort((a, b) => {\n            for (const key of ['abc', def]) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When compare method is already imported from @ember/utils.\n      code: `import { compare } from '@ember/utils';\n      something.sortBy('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\n      [...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When compare method is already imported with alias from @ember/utils.\n      code: `import { compare as comp } from '@ember/utils';\n      something.sortBy('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { compare as comp } from '@ember/utils';\n      [...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = comp(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When compare method is already imported from a package other than @ember/utils.\n      code: `import { compare as comp } from '@custom/utils';\n      something.sortBy('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\nimport { compare as comp } from '@custom/utils';\n      [...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When get method is already imported from @ember/object.\n      code: `import { compare as comp } from '@custom/utils';\n      import { get } from '@ember/object';\n      something.sortBy('abc', 'def')`,\n      output: `import { compare } from '@ember/utils';\nimport { compare as comp } from '@custom/utils';\n      import { get } from '@ember/object';\n      [...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When get method is already imported from a package other than @ember/object.\n      code: `import { compare as comp } from '@custom/utils';\n      import { get as g } from '@custom/object';\n      something.sortBy('abc', 'def')`,\n      output: `import { get } from '@ember/object';\nimport { compare } from '@ember/utils';\nimport { compare as comp } from '@custom/utils';\n      import { get as g } from '@custom/object';\n      [...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = compare(get(a, key), get(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When get method is already imported with an alias from @ember/object package.\n      code: `import { compare as comp } from '@custom/utils';\n      import { get as g } from '@ember/object';\n      something.sortBy('abc', 'def')`,\n      output: `import { compare } from '@ember/utils';\nimport { compare as comp } from '@custom/utils';\n      import { get as g } from '@ember/object';\n      [...something].sort((a, b) => {\n            for (const key of ['abc', 'def']) {\n              const compareValue = compare(g(a, key), g(b, key));\n              if (compareValue) {\n                return compareValue;\n              }\n            }\n            return 0;\n          })`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of params are passed, we will skip auto-fixing\n      code: 'something.toArray(1)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.toArray()',\n      output: '[...something]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.somethingElse.toArray()',\n      output: '[...something.somethingElse]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.uniq()',\n      output: '[...new Set(something)]',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of params are passed, we will skip auto-fixing\n      code: 'something.uniq(1)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of params are passed, we will skip auto-fixing\n      code: 'something.uniqBy()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Primitive value as an argument\n      code: 'something.uniqBy(1)',\n      output: `import { get } from '@ember/object';\nsomething.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), (item) => get(item, 1)])[0]`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Dynamic value as an argument\n      code: 'something.uniqBy(abc)',\n      output: `import { get } from '@ember/object';\nsomething.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), typeof abc === 'function' ? abc : (item) => get(item, abc)])[0]`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // get method is already imported from @ember/object package.\n      code: `import { get } from '@ember/object';\n      something.uniqBy('abc').sort()`,\n      output: `import { get } from '@ember/object';\n      something.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), (item) => get(item, 'abc')])[0].sort()`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // get method is already imported from a package other than @ember/object.\n      code: `import { get as g } from '@custom/object';\n      something.uniqBy('abc').sort()`,\n      output: `import { get } from '@ember/object';\nimport { get as g } from '@custom/object';\n      something.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), (item) => get(item, 'abc')])[0].sort()`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // get method is already imported with an alias from @ember/object package.\n      code: `import { get as g } from '@ember/object';\n      something.uniqBy('abc')`,\n      output: `import { get as g } from '@ember/object';\n      something.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), (item) => g(item, 'abc')])[0]`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When arrow function expression is passed as argument\n      code: `import { get as g } from '@ember/object';\n      something.uniqBy(() => true)`,\n      output: `import { get as g } from '@ember/object';\n      something.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), () => true])[0]`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When function expression is passed as argument\n      code: `import { get as g } from '@ember/object';\n      something.uniqBy(function test() { return true; })`,\n      output: `import { get as g } from '@ember/object';\n      something.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), function test() { return true; }])[0]`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When function expression is passed as argument\n      code: `function test() { return true; }\n      import { get as g } from '@ember/object';\n      something.uniqBy(test)`,\n      output: `function test() { return true; }\n      import { get as g } from '@ember/object';\n      something.reduce(([uniqArr, itemsSet, getterFn], item) => {\n          const val = getterFn(item);\n          if (!itemsSet.has(val)) {\n            itemsSet.add(val);\n            uniqArr.push(item);\n          }\n          return [uniqArr, itemsSet, getterFn];\n        }, [[], new Set(), typeof test === 'function' ? test : (item) => g(item, test)])[0]`,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of params are passed, we will skip auto-fixing\n      code: 'something.without()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // When unexpected number of params are passed, we will skip auto-fixing\n      code: 'something.without(1, 2)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.without(1)',\n      output: '(something.indexOf(1) > -1 ? something.filter(item => item !== 1) : something)',\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: \"something.somethingElse.without('abc')\",\n      output:\n        \"(something.somethingElse.indexOf('abc') > -1 ? something.somethingElse.filter(item => item !== 'abc') : something.somethingElse)\",\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.lastObject',\n      output: null,\n      errors: [{ messageId: 'main', type: 'MemberExpression' }],\n    },\n    {\n      code: 'something.addObject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.addObjects()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.insertAt()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.popObject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.pushObject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.pushObjects()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.removeAt()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.removeObject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.removeObjects()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.reverseObjects()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.setObjects()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.shiftObject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.unshiftObject()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.unshiftObjects()',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      code: 'something.replace(1, 2, someArray)',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Variable.\n      code: 'const foo = new Array(); foo.clear();',\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property with array value.\n      code: `\n      class MyClass {\n        foo = new Array();\n        myFunc() {\n          this.foo.clear();\n        }\n      }`,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property (private) with array value.\n      code: `\n      class MyClass {\n        #foo = new Array();\n        myFunc() {\n          this.#foo.clear();\n        }\n      }`,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property with array value (TypeScript).\n      code: `\n      class MyClass {\n        foo: Array<UploadFile> = new Array();\n        myFunc() {\n          this.foo.clear();\n        }\n      }\n      `,\n      output: null,\n      parser: require.resolve('@typescript-eslint/parser'),\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property (private) with array value (TypeScript).\n      code: `\n      class MyClass {\n        #foo: Array<UploadFile> = new Array();\n        myFunc() {\n          this.#foo.clear();\n        }\n      }\n      `,\n      output: null,\n      parser: require.resolve('@typescript-eslint/parser'),\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property definition (private) with array class (TypeScript) (does not confuse public/private properties).\n      code: `\n      class MyClass {\n        #foo: Array<UploadFile> = new Array();\n        foo: Set<UploadFile> = new Set();\n\n        myFunc() {\n          this.#foo.clear();\n        }\n      }\n      `,\n      output: null,\n      parser: require.resolve('@typescript-eslint/parser'),\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property with no value.\n      code: `\n      class MyClass {\n        foo;\n        myFunc() {\n          this.foo.clear();\n        }\n      }`,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Class property but not declared anywhere.\n      code: `\n      class MyClass {\n        myFunc() {\n          this.foo.clear();\n        }\n      }`,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Two classes (should not ignore first class property).\n      code: `\n      class MyClass1 {\n        foo = new Set();\n      }\n      class MyClass2 {\n        myFunc() {\n          this.foo.clear();\n        }\n      }`,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Nested classes (should not ignore outer class property).\n      code: `\n      class MyClass1 {\n        foo = new Set();\n        myFunc() {\n          class MyClass2 {\n            myFunc() {\n              this.foo.clear();\n            }\n          }\n        }\n      }\n      `,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n    {\n      // Nested classes (should remember first class properties).\n      code: `\n      class MyClass1 {\n        foo = new Array();\n        myFunc() {\n          class MyClass2 {}\n          this.foo.clear();\n        }\n      }\n      `,\n      output: null,\n      errors: [{ messageId: 'main', type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-arrow-function-computed-properties.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-arrow-function-computed-properties');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\nruleTester.run('no-arrow-function-computed-properties', rule, {\n  valid: [\n    'computed()',\n    'computed(function() { return 123; })',\n    'computed(\"prop\", function() { return this.prop; })',\n    'computed(\"prop\", function() { return this.prop; }).volatile()',\n    'computed.map(\"products\", function(product) { return someFunction(product); })',\n    'other(() => {})',\n    'other.computed(() => {})',\n    {\n      code: \"computed('prop', function() { return this.prop; });\",\n      options: [{ onlyThisContexts: true }],\n    },\n    {\n      code: \"computed('prop', function() { return this.prop; }).volatile();\",\n      options: [{ onlyThisContexts: true }],\n    },\n    {\n      code: 'computed(() => { return 123; });',\n      options: [{ onlyThisContexts: true }],\n    },\n    {\n      code: 'computed(() => { return \"string stuff\"; });',\n      options: [{ onlyThisContexts: true }],\n    },\n    {\n      code: 'computed(() => []);',\n      options: [{ onlyThisContexts: true }],\n    },\n    {\n      code: \"computed.map('products', product => { return someFunction(product); });\",\n      options: [{ onlyThisContexts: true }],\n    },\n  ].map(addComputedImport),\n  invalid: [\n    {\n      code: 'computed(() => { return 123; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: \"import Ember from 'ember'; Ember.computed(() => { return 123; })\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: \"computed('prop', () => { return this.prop; })\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n\n    {\n      code: \"computed('prop', () => { return this.prop; }).volatile()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: \"computed.map('products', product => { return someFunction(product); })\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: \"computed('prop', () => { return this.prop; })\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n      options: [{ onlyThisContexts: true }],\n    },\n    {\n      code: \"computed('prop', () => { return this.prop; }).volatile()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n      options: [{ onlyThisContexts: true }],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-assignment-of-untracked-properties-used-in-tracking-contexts');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n});\n\nruleTester.run('no-assignment-of-untracked-properties-used-in-tracking-contexts', rule, {\n  valid: [\n    // **********************\n    // Native class\n    // **********************\n\n    {\n      // Assignment of property which is not used as a dependent key.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed get prop1() {}\n        @computed() get prop2() {}\n        @computed('random') get prop3() {}\n        myFunction() { this.x = 123; }\n      }`,\n    },\n    {\n      // Assignment of tracked property.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked } from '@glimmer/tracking';\n      class MyClass extends Component {\n        @tracked x\n        @computed('x') get prop() {}\n        myFunction() { this.x = 123; }\n      }`,\n    },\n    {\n      // Assignment of tracked property with string literal property name.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked } from '@glimmer/tracking';\n      class MyClass extends Component {\n        @tracked 'x'\n        @computed('x') get prop() {}\n        myFunction() { this.x = 123; }\n      }`,\n    },\n    {\n      // Assignment of tracked property (with aliased `tracked` import).\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked as t } from '@glimmer/tracking';\n      class MyClass extends Component {\n        @t x\n        @computed('x') get prop() {}\n        myFunction() { this.x = 123; }\n      }`,\n    },\n    {\n      // Assignment of property in the Glimmer component args hash which is automatically tracked.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@glimmer/component';\n      class MyClass extends Component {\n        @computed('args.x') get prop() {}\n        myFunction() { this.args.x = 123; }\n      }`,\n    },\n    {\n      // Assignment of dependent key property but outside the class.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('x') get prop() {}\n      }\n      this.x = 123;`,\n    },\n    {\n      // Assignment missing `this.`.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('x') get prop() {}\n        myFunction() { x = 123; }\n      }`,\n    },\n    {\n      // Test files should be ignored.\n      filename: '/components/foo-test.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('x') get prop() {}\n        myFunction() { this.x = 123; }\n      }`,\n    },\n    {\n      // Not an Ember module file.\n      filename: '/random/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import SomeThing from 'random';\n      SomeThing.extend({\n        prop: computed('x', function() {}),\n        myFunction() { this.x = 123; }\n      })`,\n    },\n    {\n      // Assignment in separate class from computed property.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class Class1 extends Component {\n        @computed('x') get myProp() {}\n      }\n      class Class2 extends Component {\n        myFunction() { this.x = 123; }\n      }`,\n    },\n    {\n      // Without computed imports.\n      filename: '/components/foo.js',\n      code: `\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('x') get prop1() {}\n        @readOnly('y') get prop2() {}\n        myFunction() { this.x = 123; this.y = 456; }\n      }`,\n    },\n\n    // **********************\n    // Classic class\n    // **********************\n    {\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      Component.extend({\n        prop: computed('x') // This is here to make sure it is not considered in the next class.\n      });\n      Component.extend({\n        ...someProp.getProperties('propA', 'propB'),\n        prop1: computed(),\n        prop2: computed(function() {}),\n        prop3: computed('random', function() {}),\n        myFunction() { this.x = 123; }\n      })`,\n    },\n    {\n      // Imports present but no computed properties.\n      filename: '/components/foo.js',\n      code: `\n      import { readOnly, equal, gt } from '@ember/object/computed';\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      Component.extend({\n        actions: {},\n      });`,\n    },\n  ],\n  invalid: [\n    // **********************\n    // Native class\n    // **********************\n\n    {\n      // Assignment of dependent key property.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked } from '@glimmer/tracking';\n      class Class1 extends Component {\n        @tracked x; // This is included just to make sure it gets ignored in the next class.\n      }\n      class Class2 extends Component {\n        @computed('x') get myProp() {}\n        myFunction() { this.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked } from '@glimmer/tracking';\n      class Class1 extends Component {\n        @tracked x; // This is included just to make sure it gets ignored in the next class.\n      }\n      class Class2 extends Component {\n        @computed('x') get myProp() {}\n        myFunction() { set(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Assignment of dependent key property (with dependent key brace expansion / assignment property is substring of dependent key).\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('x.{a,b}.@each.z') get myProp() {}\n        myFunction() { this.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('x.{a,b}.@each.z') get myProp() {}\n        myFunction() { set(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Assignment of dependent key property (with aliased `computed` import).\n      filename: '/components/foo.js',\n      code: `\n      import { computed as c } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @c('x') get myProp() {}\n        myFunction() { this.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed as c } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @c('x') get myProp() {}\n        myFunction() { set(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Assignment of dependent key property (should use aliased `set` import instead of adding a new import).\n      filename: '/components/foo.js',\n      code: `\n      import { computed as c, set as s } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @c('x') get myProp() {}\n        myFunction() { this.x = 123; }\n      }`,\n      output: `\n      import { computed as c, set as s } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @c('x') get myProp() {}\n        myFunction() { s(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Assignment of dependent key property (inside generic class that does not extend from an Ember module)\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      class MyClass {\n        @computed('x') get myProp() {}\n        myFunction() { this.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      class MyClass {\n        @computed('x') get myProp() {}\n        myFunction() { set(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // `args` should not be treated special outside of Glimmer components.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('args.x') get prop() {}\n        myFunction() { this.args.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @computed('args.x') get prop() {}\n        myFunction() { set(this, 'args.x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Manages state of inner class separate from outer class.\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class Class1 extends Component {\n        @computed('x') get myProp() {}\n        function1() {\n          class InternalStateTracker {\n            @tracked x;\n            innerFunction() {\n              this.x = 123; // Allowed because tracked in this inner class.\n            }\n          }\n        }\n        function2() {\n          this.x = 123;\n        }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import Component from '@ember/component';\n      class Class1 extends Component {\n        @computed('x') get myProp() {}\n        function1() {\n          class InternalStateTracker {\n            @tracked x;\n            innerFunction() {\n              this.x = 123; // Allowed because tracked in this inner class.\n            }\n          }\n        }\n        function2() {\n          set(this, 'x', 123);\n        }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n\n    // **********************\n    // Macros (native class)\n    // **********************\n\n    {\n      // Simple macro (`readOnly`), renamed\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import { readOnly as ro } from '@ember/object/computed';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @ro('x') get myProp() {}\n        myFunctionX() { this.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import { readOnly as ro } from '@ember/object/computed';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @ro('x') get myProp() {}\n        myFunctionX() { set(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Simple macro (`computed.readOnly`), with `computed` rename\n      filename: '/components/foo.js',\n      code: `\n      import { computed as c } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @c.readOnly('x') get myProp() {}\n        myFunctionX() { this.x = 123; }\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed as c } from '@ember/object';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @c.readOnly('x') get myProp() {}\n        myFunctionX() { set(this, 'x', 123); }\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Macro with non-tracked arguments (`mapBy`)\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import { mapBy } from '@ember/object/computed';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @mapBy('chores', 'done', true) get myProp() {}\n        myFunction1() { this.chores = 123; }\n        myFunction2() { this.done = 123; } // Allowed since this isn't a dependent key.\n      }`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import { mapBy } from '@ember/object/computed';\n      import Component from '@ember/component';\n      class MyClass extends Component {\n        @mapBy('chores', 'done', true) get myProp() {}\n        myFunction1() { set(this, 'chores', 123); }\n        myFunction2() { this.done = 123; } // Allowed since this isn't a dependent key.\n      }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n\n    // **********************\n    // Classic class\n    // **********************\n\n    {\n      // Assignment of dependent key property\n      filename: '/components/foo.js',\n      code: `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked } from '@glimmer/tracking';\n      class MyClass extends Component {\n        @tracked x // This is here to ensure that it is ignored in the subsequent class.\n      }\n      Component.extend({\n        myProp: computed('x', function() {}),\n        myFunction() { this.x = 123; }\n      })`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed } from '@ember/object';\n      import Component from '@ember/component';\n      import { tracked } from '@glimmer/tracking';\n      class MyClass extends Component {\n        @tracked x // This is here to ensure that it is ignored in the subsequent class.\n      }\n      Component.extend({\n        myProp: computed('x', function() {}),\n        myFunction() { set(this, 'x', 123); }\n      })`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n\n    // **********************\n    // Macros (classic class)\n    // **********************\n\n    {\n      // Simple macro (`readOnly`), renamed\n      filename: '/components/foo.js',\n      code: `\n      import { readOnly as ro } from '@ember/object/computed';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: ro('x'),\n        myFunction() { this.x = 123; }\n      })`,\n      output: `\n      import { set } from '@ember/object';\nimport { readOnly as ro } from '@ember/object/computed';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: ro('x'),\n        myFunction() { set(this, 'x', 123); }\n      })`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Simple macro (`computed.readOnly`), with `computed` rename\n      filename: '/components/foo.js',\n      code: `\n      import { computed as c } from '@ember/object';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: c.readOnly('x'),\n        myFunction() { this.x = 123; }\n      })`,\n      output: `\n      import { set } from '@ember/object';\nimport { computed as c } from '@ember/object';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: c.readOnly('x'),\n        myFunction() { set(this, 'x', 123); }\n      })`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n\n    {\n      // Macro with non-tracked arguments (`mapBy`)\n      filename: '/components/foo.js',\n      code: `\n      import { mapBy } from '@ember/object/computed';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: mapBy('chores', 'done', true),\n        myFunction1() { this.chores = 123; },\n        myFunction2() { this.done = 123; } // Allowed since this isn't a dependent key.\n      })`,\n      output: `\n      import { set } from '@ember/object';\nimport { mapBy } from '@ember/object/computed';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: mapBy('chores', 'done', true),\n        myFunction1() { set(this, 'chores', 123); },\n        myFunction2() { this.done = 123; } // Allowed since this isn't a dependent key.\n      })`,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Custom macro for direct strings\n      filename: '/components/foo.js',\n      code: `\n      import { rejectBy } from 'custom-macros/custom';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: rejectBy('chores', 'done', true),\n        myFunction1() { this.chores = 123; },\n        myFunction2() { this.done = 123; }, // Allowed since this isn't a dependent key.\n      })`,\n      output: `\n      import { set } from '@ember/object';\nimport { rejectBy } from 'custom-macros/custom';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: rejectBy('chores', 'done', true),\n        myFunction1() { set(this, 'chores', 123); },\n        myFunction2() { this.done = 123; }, // Allowed since this isn't a dependent key.\n      })`,\n      options: [\n        {\n          extraMacros: [\n            {\n              path: 'custom-macros/custom',\n              name: 'rejectBy',\n              argumentFormat: [\n                {\n                  strings: {\n                    count: 1,\n                  },\n                },\n              ],\n            },\n          ],\n        },\n      ],\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // Custom macro for object values\n      filename: '/components/foo.js',\n      code: `\n      import { customComputed } from 'custom-macros/ind';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: customComputed.t('unused', { tKey: 'dependency.key' }),\n        myFunction1() { this.dependency = 123; },\n        myFunction2() { this.unused = 123; }, // Allowed since this isn't a dependent key.\n        myFunction3() { this.tKey = 123; }, // Allowed since this is a key, not a value.\n      })`,\n      output: `\n      import { set } from '@ember/object';\nimport { customComputed } from 'custom-macros/ind';\n      import Component from '@ember/component';\n      Component.extend({\n        myProp: customComputed.t('unused', { tKey: 'dependency.key' }),\n        myFunction1() { set(this, 'dependency', 123); },\n        myFunction2() { this.unused = 123; }, // Allowed since this isn't a dependent key.\n        myFunction3() { this.tKey = 123; }, // Allowed since this is a key, not a value.\n      })`,\n      options: [\n        {\n          extraMacros: [\n            {\n              path: 'custom-macros/custom',\n              name: 't',\n              indexPath: 'custom-macros/ind',\n              indexName: 'customComputed',\n              argumentFormat: [\n                {\n                  objects: {\n                    index: 1,\n                  },\n                },\n              ],\n            },\n          ],\n        },\n      ],\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-at-ember-render-modifiers.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-at-ember-render-modifiers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\nruleTester.run('no-at-ember-render-modifiers', rule, {\n  valid: [\n    'import x from \"x\"',\n    '',\n    \"import { x } from 'foo';\",\n    \"import x from '@ember/foo';\",\n    \"import x from '@ember/render-modifiers-foo';\",\n  ],\n  invalid: [\n    {\n      code: 'import \"@ember/render-modifiers\";',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: 'import x from \"@ember/render-modifiers\";',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: 'import { x } from \"@ember/render-modifiers\";',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: 'import didInsert from \"@ember/render-modifiers/modifiers/did-insert\";',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: 'import didUpdate from \"@ember/render-modifiers/modifiers/did-update\";',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: 'import willDestroy from \"@ember/render-modifiers/modifiers/will-destroy\";',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-attrs-in-components.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-attrs-in-components');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\nruleTester.run('no-attrs-in-components', rule, {\n  valid: [\n    \"import Component from '@ember/component'; Component.extend({ init() { this.foo.bar; }  });\",\n    \"import Component from '@ember/component'; Component.extend({ ...foo });\",\n    \"import Component from '@ember/component'; class MyComponent extends Component { init() { this.foo.bar; } }\",\n    \"import Component from '@glimmer/component'; class MyComponent extends Component { constructor() { this.foo.bar; } }\",\n\n    // After a component:\n    \"import Component from '@ember/component'; Component.extend({}); this.attrs.foo;\",\n    \"import Component from '@ember/component'; class MyComponent extends Component {} this.attrs.foo;\",\n\n    // Not a component:\n    'Random.extend({ init() { this.attrs.foo; }  });',\n    \"import Component from 'not-a-component'; class MyComponent extends Component { init() { this.attrs.foo; } }\",\n  ],\n\n  invalid: [\n    {\n      code: \"import Component from '@ember/component'; Component.extend({ init() { this.attrs.foo; } });\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n    {\n      code: \"import Component from '@ember/component'; class MyComponent extends Component { init() { this.attrs.foo; } }\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n    {\n      code: \"import Component from '@glimmer/component'; class MyComponent extends Component { constructor() { this.attrs.foo; } }\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-attrs-snapshot.js",
    "content": "const rule = require('../../../lib/rules/no-attrs-snapshot');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\neslintTester.run('no-attrs-snapshot', rule, {\n  valid: [\n    `\n        export default Ember.Component({\n          init() {\n            this._super(...arguments);\n            this._valueCache = this.value;\n            this.updated = false;\n          },\n          didReceiveAttrs() {\n            if (this._valueCache !== this.value) {\n              this._valueCache = this.value;\n              this.set('updated', true);\n            } else {\n              this.set('updated', false);\n            }\n          },\n          ...foo\n        });`,\n    `\n        export default Ember.Component({\n          init() {\n            this._super(...arguments);\n            this._valueCache = this.value;\n            this.updated = false;\n          },\n          didUpdateAttrs() {\n            if (this._valueCache !== this.value) {\n              this._valueCache = this.value;\n              this.set('updated', true);\n            } else {\n              this.set('updated', false);\n            }\n          }\n        });`,\n  ],\n  invalid: [\n    {\n      code: `\n        export default Ember.Component({\n          init() {\n            this._super(...arguments);\n            this.updated = false;\n          },\n          didReceiveAttrs(attrs) {\n            let { newAttrs, oldAttrs } = attrs;\n            if ((newAttrs && oldAttrs) && newAttrs.value !== oldAttrs.value) {\n              this.set('updated', true);\n            } else {\n              this.set('updated', false);\n            }\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        export default Ember.Component({\n          init() {\n            this._super(...arguments);\n            this.updated = false;\n          },\n          didUpdateAttrs(attrs) {\n            let { newAttrs, oldAttrs } = attrs;\n            if ((newAttrs && oldAttrs) && newAttrs.value !== oldAttrs.value) {\n              this.set('updated', true);\n            } else {\n              this.set('updated', false);\n            }\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-builtin-form-components.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-builtin-form-components');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE } = rule;\nconst parserOptions = {\n  ecmaVersion: 2022,\n  sourceType: 'module',\n};\nconst ruleTester = new RuleTester({ parserOptions });\n\nruleTester.run('no-builtin-form-components', rule, {\n  valid: [\n    \"import Component from '@ember/component';\",\n    \"import { setComponentTemplate } from '@ember/component';\",\n    \"import { helper } from '@ember/component/helper';\",\n    \"import { Input } from 'custom-component';\",\n    \"import { Textarea } from 'custom-component';\",\n  ],\n\n  invalid: [\n    {\n      code: \"import { Input } from '@ember/component';\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportSpecifier',\n        },\n      ],\n    },\n    {\n      code: \"import { Textarea } from '@ember/component';\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportSpecifier',\n        },\n      ],\n    },\n    {\n      code: \"import { Input, Textarea } from '@ember/component';\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportSpecifier',\n        },\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportSpecifier',\n        },\n      ],\n    },\n    {\n      code: \"import { Input as EmberInput } from '@ember/component';\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportSpecifier',\n        },\n      ],\n    },\n    {\n      code: \"import { Textarea as EmberTextarea } from '@ember/component';\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportSpecifier',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-capital-letters-in-routes.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-capital-letters-in-routes');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\neslintTester.run('no-capital-letters-in-routes', rule, {\n  valid: [\n    'this.route(\"sign-in\");',\n    'this.route(\"news\", { path: \"/:news_id\" });',\n    `\n        const routeName=\"about\";\n        this.route(routeName);\n        this.route(DASH_TAB.ACTIVITY);`,\n  ],\n\n  invalid: [\n    {\n      code: 'this.route(\"Sign-in\");',\n      output: null,\n      errors: [\n        {\n          message: \"Unexpected capital letter in route's name\",\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"hOme\");',\n      output: null,\n      errors: [\n        {\n          message: \"Unexpected capital letter in route's name\",\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"DASH_TAB.ACTIVITY\");',\n      output: null,\n      errors: [\n        {\n          message: \"Unexpected capital letter in route's name\",\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-classic-classes.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-classic-classes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE_NO_CLASSIC_CLASSES: ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('no-classic-classes', rule, {\n  valid: [\n    `\n      import Component from '@ember/component';\n\n      export default class MyComponent extends Component {}\n    `,\n    `\n      import Component from '@ember/component';\n      import Evented from '@ember/object/Evented'; // This is a mixin.\n\n      export default class MyComponent extends Component.extend(Evented) {} // Allowed to extend from mixins only.\n    `,\n    `\n      import SomeOtherThing from 'some-other-library';\n\n      export default SomeOtherThing.extend({});\n    `,\n    `\n      import Component from '@ember/component';\n      const notAnObject = 123;\n      export default Component.extend(notAnObject); // Unexpected variable type passed.\n    `,\n    'export default Component.extend({});', // No import\n\n    'this.extend();',\n    'this.foo.extend();',\n    'this.foo.bar.extend();',\n  ],\n\n  invalid: [\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend();\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend({});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        import Evented from '@ember/object/Evented'; // This is a mixin.\n        export default Component.extend(Evented, {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        const myMockComponent = {};\n        export default Component.extend(myMockComponent); // Object variable provided.\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component.extend() {};\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component.extend({}) {};\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        import Evented from '@ember/object/evented'; // This is a mixin.\n        export default class MyComponent extends Component.extend(Evented, {}) {}; // Disallowed because an object is extended from.\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import DS from 'ember-data';\n        export default DS.Model.extend({});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Model from '@ember-data/model';\n        export default Model.extend({});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import CustomClass from 'my-custom-addon';\n        export default CustomClass.extend({});\n      `,\n      output: null,\n      options: [\n        {\n          additionalClassImports: ['my-custom-addon'],\n        },\n      ],\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-classic-components.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-classic-components');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE } = rule;\nconst parserOptions = {\n  ecmaVersion: 2022,\n  sourceType: 'module',\n};\nconst ruleTester = new RuleTester({ parserOptions });\n\nruleTester.run('no-classic-components', rule, {\n  valid: [\n    \"import Component from '@glimmer/component';\",\n    \"import { setComponentTemplate } from '@ember/component';\",\n    \"import { helper } from '@ember/component/helper';\",\n  ],\n\n  invalid: [\n    {\n      code: \"import Component from '@ember/component';\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'ImportDefaultSpecifier',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-component-lifecycle-hooks.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-component-lifecycle-hooks');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE_NO_COMPONENT_LIFECYCLE_HOOKS: ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('no-component-lifecycle-hooks', rule, {\n  valid: [\n    // Legitimate Glimmer component lifecycle hooks:\n    `\n      import Component from '@glimmer/component';\n      export default class MyComponent extends Component {\n        constructor() {}\n        willDestroy() {}\n        foo() {}\n      }\n    `,\n\n    // Not extending from the component:\n    `\n      import Component from '@glimmer/component';\n      export default class MyClass {\n        didUpdate() {}\n      }\n    `,\n\n    // Just an object:\n    `\n      export default {\n        didUpdate() {},\n      }\n    `,\n\n    // Not a lifecycle hook:\n    `\n      import Component from '@ember/component';\n      export default Component.extend({\n        foo() {},\n      });\n    `,\n\n    // Legitimate classic component lifecycle hook:\n    `\n      import Component from '@ember/component';\n      export default Component.extend({\n        willDestroy() {},\n      });\n    `,\n    `\n      import Component from '@ember/component';\n      export const Component1 = Component.extend({\n        willDestroy() {},\n      });\n      export const Component2 = Component.extend({\n        willDestroy() {},\n      });\n    `,\n    `\n      import Component from '@ember/component';\n      export default class extends Component {\n        willDestroy() {}\n      }\n    `,\n\n    // Just an EmberObject:\n    `\n      export default EmberObject.extend({\n        didUpdate() {},\n      });\n    `,\n\n    // Hooks in a random object after a component class:\n    `\n      import Component from '@ember/component';\n      export default class MyComponent extends Component {}\n\n      const someRandomClassOrObject = { didDestroyElement() { } };\n    `,\n    `\n      import Component from '@ember/component';\n      export default Component.extend({});\n\n      const someRandomClassOrObject = { didDestroyElement() { } };\n    `,\n    `\n      import Component from '@ember/component';\n\n      export default class CommentInput extends Component.extend() {\n      }\n\n      export class CommentInput2 {\n        didDestroyElement() {}\n      }\n    `,\n  ],\n\n  invalid: [\n    // Glimmer component using classic Ember component lifecycle hook:\n    {\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          myMethod() {\n            class FooBarClass {}\n          }\n          didDestroyElement() {}\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'MethodDefinition',\n        },\n      ],\n    },\n\n    // Classic component using classic Ember component lifecycle hook (native class):\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {\n          myMethod() {\n            class FooBarClass {}\n          }\n          didDestroyElement() {}\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'MethodDefinition',\n        },\n      ],\n    },\n\n    // Classic component using classic Ember component lifecycle hook (classic class):\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend({\n          test: computed('', function () {}),\n          didDestroyElement() {}\n        })\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Identifier',\n        },\n      ],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n\n        export const Component2 = Component.extend({\n          didDestroyElement() {},\n        });\n\n        export const Component1 = Component.extend({\n          willDestroy() {},\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Identifier',\n        },\n      ],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n\n        export default class CommentInput extends Component.extend() {\n          didDestroyElement() {}\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'MethodDefinition',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-computed-properties-in-native-classes.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-computed-properties-in-native-classes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\n\nruleTester.run('no-computed-properties-in-native-classes', rule, {\n  valid: [\n    `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n\n      export default Ember.Component.extend({});\n    `,\n    `\n      import { alias, or, and } from '@ember/object/computed';\n      import Component from '@ember/component';\n\n      export default Ember.Component.extend({});\n    `,\n    `\n      import { tracked } from '@glimmer/tracking';\n      import Component from '@ember/component';\n\n      export default class MyComponent extends Component {}\n    `,\n    `\n      import { randomThing } from '@ember/object';\n      import Component from '@ember/component';\n\n      export default class MyComponent extends Component {}\n    `,\n    {\n      code: `\n        import Component from '@ember/component';\n\n        export default class MyComponent extends Component {}\n      `,\n      options: [\n        {\n          ignoreClassic: true,\n        },\n      ],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n\n        export default class MyComponent extends Component {}\n      `,\n      options: [\n        {\n          ignoreClassic: false,\n        },\n      ],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        import classic from 'ember-classic-decorator';\n\n        @classic\n        export default class MyComponent extends Component {}\n      `,\n      options: [\n        {\n          ignoreClassic: true,\n        },\n      ],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        import classic from 'ember-classic-decorator';\n\n        @classic\n        export default class MyComponent extends Component {}\n      `,\n      options: [\n        {\n          ignoreClassic: false,\n        },\n      ],\n    },\n    {\n      code: `\n        import { computed } from '@ember/object';\n        import Component from '@ember/component';\n        import classic from 'ember-classic-decorator';\n\n        @classic\n        export default class MyComponent extends Component {}\n      `,\n      options: [\n        {\n          ignoreClassic: true,\n        },\n      ],\n    },\n    {\n      code: `\n        import { computed } from '@ember/object';\n        import Component from '@ember/component';\n        import classic from 'ember-classic-decorator';\n\n        @classic\n        export default class MyComponent extends Component {}\n      `,\n      options: [], // default options should be: [{ ignoreClassic: true }]\n    },\n\n    // Unrelated import statements:\n    \"import EmberObject from '@ember/object';\",\n    \"import { run } from '@ember/runloop';\",\n    \"import { run as renamedRun } from '@ember/runloop';\",\n\n    // File with both native class and .extend() should not flag computed import\n    `\n      import { computed } from '@ember/object';\n      import Component from '@ember/component';\n\n      class Helper {}\n\n      export default Component.extend({\n        fullName: computed('firstName', 'lastName', function() {})\n      });\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n      import { computed } from '@ember/object';\n\n      export default class MyComponent extends Component {\n\n      }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n      import { computed as thinking } from '@ember/object';\n\n      export default class MyComponent extends Component {\n\n      }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n      import { and, or, alias } from '@ember/object/computed';\n\n      export default class MyComponent extends Component {\n\n      }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n        import { computed } from '@ember/object';\n        import Component from '@ember/component';\n\n        export default class MyComponent extends Component {}\n      `,\n      output: null,\n      options: [\n        {\n          ignoreClassic: true,\n        },\n      ],\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n        import { computed } from '@ember/object';\n        import Component from '@ember/component';\n\n        export default class MyComponent extends Component {}\n      `,\n      output: null,\n      options: [\n        {\n          ignoreClassic: false,\n        },\n      ],\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n        import { computed } from '@ember/object';\n        import Component from '@ember/component';\n        import classic from 'ember-classic-decorator';\n\n        @classic\n        export default class MyComponent extends Component {}\n      `,\n      output: null,\n      options: [\n        {\n          ignoreClassic: false,\n        },\n      ],\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-controller-access-in-routes.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-controller-access-in-routes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-controller-access-in-routes', rule, {\n  valid: [\n    `\n      import Route from '@ember/routing/route';\n      export default class MyRoute extends Route {\n        setupController(controller, ...args) {\n          super.setupController(controller, ...args);\n          const foo = controller.foo;\n        }\n      }\n    `,\n    `\n      import Route from '@ember/routing/route';\n      export default Route.extend({\n        setupController(controller, ...args) {\n          this._super(controller, ...args);\n          const foo = controller.foo;\n        }\n      });\n    `,\n    `\n      import Route from '@ember/routing/route';\n      export default class MyRoute extends Route {\n        resetController(controller, ...args) {\n          super.resetController(controller, ...args);\n          const foo = controller.foo;\n        }\n      }\n    `,\n    `\n      import Route from '@ember/routing/route';\n      export default Route.extend({\n        resetController(controller, ...args) {\n          this._super(controller, ...args);\n          const foo = controller.foo;\n        }\n      });\n    `,\n    `\n      import Route from '@ember/routing/route';\n      export default Route.extend({\n        actions: {\n          myAction() {\n            const { foo } = this;\n            const { controller } = bar;\n          },\n          myAction2() {\n            const controller = this;\n          },\n          myAction3() {\n            let controller4;\n          },\n        },\n      });\n    `,\n\n    `\n      import Component from '@ember/component';\n      import { action, get } from '@ember/object';\n      export default class MyComponent extends Component {\n        @action\n        myAction() {\n          const controller = this.controller;\n        }\n      }\n    `,\n    `\n      import Route from '@ember/routing/route';\n      export default class MyRoute extends Route {}\n      this.controller;\n      this.controllerFor('my');\n    `,\n    `\n      import Route from '@ember/routing/route';\n      export default Route.extend({});\n      this.controller;\n      this.controllerFor('my');\n    `,\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = this.controllerFor('my');\n          }\n        }\n      `,\n      options: [{ allowControllerFor: true }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        export default Route.extend({\n          actions: {\n            myAction() {\n              const controller = this.controllerFor('my');\n            },\n          },\n        });\n      `,\n      options: [{ allowControllerFor: true }],\n    },\n  ],\n  invalid: [\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = this.controller;\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        export default Route.extend({\n          actions: {\n            myAction() {\n              const controller = this.controller;\n            },\n          },\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'MemberExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = this.controllerFor('my');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = this.controllerFor('my');\n          }\n        }\n      `,\n      output: null,\n      options: [{ allowControllerFor: false }],\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = this.get('controller');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action, get } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = get(this, 'controller');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action, get as g } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const controller = g(this, 'controller');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const { foo, controller } = this.getProperties('foo', 'controller');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const { foo, controller } = this.getProperties(['foo', 'controller']);\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action, getProperties } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const { foo, controller } = getProperties(this, 'foo', 'controller');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action, getProperties } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const { foo, controller } = getProperties(this, ['foo', 'controller']);\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action, getProperties as gp } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const { controller } = gp(this, 'controller');\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Route from '@ember/routing/route';\n        import { action } from '@ember/object';\n        export default class MyRoute extends Route {\n          @action\n          myAction() {\n            const { controller } = this;\n          }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-controllers.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-controllers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('no-controllers', rule, {\n  valid: [\n    // Classic class with queryParams.\n    `\n      import Controller from '@ember/controller';\n      export default Controller.extend({\n        someFunction() {},\n        query: null,\n        sortType: null,\n        sortOrder: null,\n        queryParams: ['query', 'sortType', 'sortOrder']\n      });\n    `,\n    // Classic class with queryParams with string literal property name.\n    `\n      import Controller from '@ember/controller';\n      export default Controller.extend({\n        'queryParams': ['query', 'sortType', 'sortOrder']\n      });\n    `,\n    // Classic class with queryParams: checks object argument from variable.\n    `\n      import Controller from '@ember/controller';\n      const body = { queryParams: ['query'] };\n      export default Controller.extend(body);\n    `,\n    // Classic class with queryParams: checks any object argument.\n    `\n      import Controller from '@ember/controller';\n      export default Controller.extend({ queryParams: ['query'] }, {});\n    `,\n    // Native class with queryParams.\n    `\n      import Controller from '@ember/controller';\n      export default class ArticlesController extends Controller {\n        get filteredArticles() {}\n        @tracked category = null;\n        queryParams = ['category'];\n      }\n    `,\n    // Native class with queryParams with string literal property name.\n    `\n      import Controller from '@ember/controller';\n      export default class ArticlesController extends Controller {\n        'queryParams' = ['category'];\n      }\n    `,\n  ],\n\n  invalid: [\n    // ***************\n    // Legacy classes:\n    // ***************\n\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default Controller.extend();\n      `,\n      output: null,\n      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default Controller.extend(SomeMixin);\n      `,\n      output: null,\n      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default Controller.extend({});\n      `,\n      output: null,\n      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default Controller.extend({\n          randomProperty: true,\n          randomFunction() {}\n        });\n      `,\n      output: null,\n      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],\n    },\n    {\n      // With mixin.\n      code: `\n        import Controller from '@ember/controller';\n        export default Controller.extend(SomeMixin, {\n          randomProperty: true,\n          randomFunction() {}\n        });\n      `,\n      output: null,\n      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],\n    },\n\n    // ***************\n    // Native classes:\n    // ***************\n\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default class ArticlesController extends Controller {}\n      `,\n      output: null,\n      errors: [{ type: 'ClassDeclaration', message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import Controller from '@ember/controller';\n        export default class ArticlesController extends Controller {\n          randomProperty = true;\n          get filteredArticles() {}\n        }\n      `,\n      output: null,\n      errors: [{ type: 'ClassDeclaration', message: ERROR_MESSAGE }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-current-route-name.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-current-route-name');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('no-current-route-name', rule, {\n  valid: [\n    \"assert.equal(currentURL(), '/foo');\",\n    \"assert.equal(currentRouteName(), '/foo');\",\n\n    `\n      import { currentURL } from '@ember/test-helpers';\n\n      assert.equal(currentURL(), '/foo');\n    `,\n\n    // who knows...\n    `\n      import { currentURL as currentRouteName } from '@ember/test-helpers';\n\n      assert.equal(currentRouteName(), '/foo');\n    `,\n  ],\n\n  invalid: [\n    {\n      code: `\n        import { currentRouteName } from '@ember/test-helpers';\n\n        assert.equal(currentRouteName(), '/foo');\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, column: 22 }],\n    },\n    {\n      code: `\n        import { currentRouteName as foo } from '@ember/test-helpers';\n\n        assert.equal(foo(), '/foo');\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, column: 22 }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-deeply-nested-dependent-keys-with-each.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-deeply-nested-dependent-keys-with-each');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\nruleTester.run('no-deeply-nested-dependent-keys-with-each', rule, {\n  valid: [\n    \"import Ember from 'ember'; Ember.computed(function() {})\",\n    \"import Ember from 'ember'; Ember.computed('foo', function() {})\",\n    \"import Ember from 'ember'; Ember.computed('foo', function() {}).readOnly()\",\n    \"import Ember from 'ember'; Ember.computed('foo.bar', function() {})\",\n    \"import Ember from 'ember'; Ember.computed('foo.bar.@each.baz', function() {})\",\n    \"import Ember from 'ember'; Ember.computed('foo.@each.bar', function() {})\",\n    \"import Ember from 'ember'; Ember.computed('foo.@each.{bar,baz}', function() {})\",\n    \"import { computed } from '@ember/object'; computed(function() {})\",\n    \"import { computed } from '@ember/object'; computed('foo', function() {})\",\n    \"import { computed } from '@ember/object'; computed('foo', function() {}).readOnly()\",\n    \"import { computed } from '@ember/object'; computed('foo.bar', function() {})\",\n    \"import { computed } from '@ember/object'; computed('foo.bar.@each.baz', function() {})\",\n    \"import { computed } from '@ember/object'; computed('foo.@each.bar', function() {})\",\n    \"import { computed } from '@ember/object'; computed('foo.@each.{bar,baz}', function() {})\",\n\n    // Not Ember's `computed` function:\n    \"otherClass.computed('foo.@each.bar.baz', function() {})\",\n    \"otherClass.myFunction('foo.@each.bar.baz', function() {})\",\n    \"myFunction('foo.@each.bar.baz', function() {})\",\n    \"import Ember from 'ember'; Ember.myFunction('foo.@each.bar.baz', function() {})\",\n    \"import { computed } from '@ember/object'; computed.unrelatedFunction('foo.@each.bar.baz', function() {})\",\n    \"import Ember from 'ember'; Ember.computed.unrelatedFunction('foo.@each.bar.baz', function() {})\",\n  ],\n  invalid: [\n    {\n      code: \"import Ember from 'ember'; Ember.computed('foo.@each.bar.baz', function() {})\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import Ember from 'ember'; Ember.computed('foo.@each.bar.baz', function() {}).readOnly()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import Ember from 'ember'; Ember.computed('foo.@each.bar.[]', function() {})\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import Ember from 'ember'; Ember.computed('foo.@each.bar.@each.baz', function() {})\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { computed } from '@ember/object'; computed('foo.@each.bar.baz', function() {})\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { computed } from '@ember/object'; computed('foo.@each.bar.baz', function() {}).readOnly()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { computed } from '@ember/object'; computed('foo.@each.bar.[]', function() {})\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { computed } from '@ember/object'; computed('foo.@each.bar.@each.baz', function() {})\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { computed } from '@ember/object'; class Test { @computed('foo.@each.bar.baz') get someProp() { return true; } }\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-deprecated-router-transition-methods.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-deprecated-router-transition-methods');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n});\n\nfunction validRouteClassUsage(serviceDefinition) {\n  return {\n    filename: 'routes/index.js',\n    code: `\n    import Route from '@ember/routing/route';\n    import { inject as service } from '@ember/service';\n\n    export default class SettingsRoute extends Route {\n      ${serviceDefinition}\n      @service session;\n\n      beforeModel() {\n        if (!this.session.isAuthenticated) {\n          this.router.transitionTo('login');\n        }\n      }\n    }`,\n  };\n}\n\nfunction validRouteExtendUsage(serviceDefinition) {\n  return {\n    filename: 'routes/index.js',\n    code: `\n    import Route from '@ember/routing/route';\n    import { inject as service } from '@ember/service';\n\n    export default Route.extend({\n      ${serviceDefinition}\n      session: service(),\n\n      beforeModel() {\n        if (!this.session.isAuthenticated) {\n          this.router.transitionTo('login');\n        }\n      }\n    })`,\n  };\n}\n\nfunction invalidRouteClassUsage(serviceDefinition, { routerServiceName, methodUsed }) {\n  return {\n    filename: 'routes/index.js',\n    errors: [\n      {\n        messageId: 'main',\n        data: { methodUsed, desiredMethod: methodUsed, moduleType: 'Route' },\n        type: 'MemberExpression',\n      },\n    ],\n    code: `\n    import Route from '@ember/routing/route';\n    import { inject as service } from '@ember/service';\n\n    export default class SettingsRoute extends Route {\n      ${serviceDefinition}\n      @service session;\n\n      beforeModel() {\n        if (!this.session.isAuthenticated) {\n          this.${methodUsed}('login');\n        }\n      }\n    }`,\n    output: `\n    import Route from '@ember/routing/route';\n    import { inject as service } from '@ember/service';\n\n    export default class SettingsRoute extends Route {\n      ${serviceDefinition}\n      @service session;\n\n      beforeModel() {\n        if (!this.session.isAuthenticated) {\n          this.${routerServiceName ?? 'router'}.${methodUsed}('login');\n        }\n      }\n    }`,\n  };\n}\n\nfunction invalidRouteExtendUsage(serviceDefinition, { routerServiceName, methodUsed }) {\n  return {\n    filename: 'routes/index.js',\n    errors: [\n      {\n        messageId: 'main',\n        data: { methodUsed, desiredMethod: methodUsed, moduleType: 'Route' },\n        type: 'MemberExpression',\n      },\n    ],\n    code: `\n    import Route from '@ember/routing/route';\n    import { inject as service } from '@ember/service';\n\n    export default Route.extend({\n      ${serviceDefinition}\n      session: service(),\n\n      beforeModel() {\n        if (!this.session.isAuthenticated) {\n          this.${methodUsed}('login');\n        }\n      }\n    })`,\n    output: `\n    import Route from '@ember/routing/route';\n    import { inject as service } from '@ember/service';\n\n    export default Route.extend({\n      ${serviceDefinition}\n      session: service(),\n\n      beforeModel() {\n        if (!this.session.isAuthenticated) {\n          this.${routerServiceName ?? 'router'}.${methodUsed}('login');\n        }\n      }\n    })`,\n  };\n}\n\nruleTester.run('no-deprecated-router-transition-methods', rule, {\n  valid: [\n    // Route Uses RouterService.transitionTo with different service injection types\n    validRouteClassUsage('@service router'),\n    validRouteClassUsage('@service() router'),\n    validRouteClassUsage(\"@service('router') router\"),\n\n    // Legacy .extends Route Uses RouterService.transitionTo with different service injection types\n    validRouteExtendUsage('router: service(),'),\n    validRouteExtendUsage(\"router: service('router'),\"),\n\n    // Route Uses RouterService.replaceWith\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service('router') router;\n        @service session;\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.replaceWith('login');\n          }\n        }\n      }`,\n    },\n    // Controller Uses RouterService.transitionTo\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      import { action } from '@ember/object';\n\n      export default class NewPostController1 extends Controller {\n        @service('router') router;\n\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.router.transitionTo('post', post.id);\n        }\n      }`,\n    },\n    // Controller Uses RouterService.replaceWith\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      import { action } from '@ember/object';\n\n      export default class NewPostController1 extends Controller {\n        @service('router') router;\n\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.router.replaceWith('post', post.id);\n        }\n      }`,\n    },\n    // Rule does not fire in components or modules outside of controller/routes\n    {\n      filename: 'components/index.js',\n      code: `\n      import Component from '@ember/component';\n      import { action } from '@ember/object';\n\n      export default class NewPostComponent extends Component {\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.transitionTo('post', post.id);\n        }\n      }`,\n    },\n    // Test ignore rule in non Ember classes\n    {\n      filename: 'routes/index.js',\n      code: `\n      export class Settings {\n        model() {\n          this.transitionTo('index')\n        }\n      }`,\n    },\n\n    // Test rule in class returned by function\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      import { action } from '@ember/object';\n\n      export default function saver() {\n        return class NewPostController1 extends Controller {\n          @service('router') router;\n\n          @action\n          async save({ title, text }) {\n            let post = this.store.createRecord('post', { title, text });\n            await post.save();\n            return this.router.replaceWith('post', post.id);\n          }\n        }\n      }`,\n    },\n\n    // Test rule is not triggered by invalid nested classes\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n\n      export default class NewPostController1 extends Controller {\n        createSetting() {\n          return class Settings {\n            model() {\n              this.transitionTo('index');\n            }\n          };\n        }\n      }`,\n    },\n\n    // Test Multiple Classes in One File\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export class SettingsIndexRoute extends Route {\n        model() {\n          return [];\n        }\n      }\n\n      export class SettingsDetailRoute extends Route {\n        @service('settings') settingsService;\n\n        async model(id) {\n          return new Setting(await this.settingsService.find(id));\n        }\n      }\n\n      export class SettingsRoute extends Route {\n        @service() router;\n        @service session;\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      }`,\n    },\n\n    // Does not error on .create\n    {\n      filename: 'utils/loads-user-controller.js',\n      code: `\n      import Controller from '@ember/controller';\n\n      const myObj = Controller.create();`,\n    },\n\n    // Does not error on empty .extend\n    {\n      filename: 'utils/loads-user-controller.js',\n      code: `\n      import Controller from '@ember/controller';\n\n      const myObj = Controller.extend()`,\n    },\n\n    // Does not error when using mixin with native class (common for validations)\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import SomeMixin from './my-mixin';\n\n      export default class FoobarTestError extends Controller.extend(SomeMixin) {\n      }`,\n    },\n\n    // Does not error when using Mixin\n    {\n      filename: 'controller/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import SomeMixin from './my-mixin';\n\n      export default Component.extend(SomeMixin, {\n      });`,\n    },\n\n    // Does not error when dot access decorator is used\n    {\n      filename: 'controllers/dot-access.js',\n      code: `\n      import Controller from '@ember/controller';\n      import SomeMixin from './my-mixin';\n      import EmberObject, { computed } from '@ember/object';\n\n      export default class FoobarTestError extends Controller {\n        @computed.reads('model.actors') actors;\n      }`,\n    },\n\n    // Does not error when `this` is from a test context\n    {\n      filename: 'tests/application/output-demos-test.ts',\n      code: `\n      import Controller from '@ember/controller';\n      import { visit } from '@ember/test-helpers';\n      import { module, test } from 'qunit';\n      import { setupApplicationTest } from 'ember-qunit';\n\n      module('Output > Demos', function (hooks) {\n        setupApplicationTest(hooks);\n\n        module('The output frame renders every demo', function () {\n            test('example', async function (assert) {\n              class FakeController extends Controller {}\n              this.owner.register('controller:edit', FakeController);\n              await visit('/edit');\n            });\n        });\n      });\n\n      `,\n    },\n  ],\n  invalid: [\n    // Route Uses RouterService.transitionTo with different service injection types\n    invalidRouteClassUsage('@service router', { methodUsed: 'transitionTo' }),\n    invalidRouteClassUsage('@service() router', { methodUsed: 'transitionTo' }),\n    invalidRouteClassUsage(\"@service('router') router\", { methodUsed: 'transitionTo' }),\n    invalidRouteClassUsage(\"@service('router') routerService\", {\n      methodUsed: 'transitionTo',\n      routerServiceName: 'routerService',\n    }),\n\n    // Legacy .extends Route Uses RouterService.transitionTo with different service injection types\n    invalidRouteExtendUsage('router: service(),', { methodUsed: 'transitionTo' }),\n    invalidRouteExtendUsage(\"router: service('router'),\", {\n      methodUsed: 'transitionTo',\n    }),\n    invalidRouteExtendUsage(\"routerMcRouteFace: service('router'),\", {\n      methodUsed: 'transitionTo',\n      routerServiceName: 'routerMcRouteFace',\n    }),\n\n    // Legacy .extends without an existing router service injection\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default Route.extend({\n        session: service(),\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n      })`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default Route.extend({\n        router: service('router'),\nsession: service(),\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      })`,\n      errors: [\n        {\n          messageId: 'main',\n          data: { methodUsed: 'transitionTo', desiredMethod: 'transitionTo', moduleType: 'Route' },\n          type: 'MemberExpression',\n        },\n      ],\n    },\n\n    // Legacy .extend with mixin as first argument should not crash\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default Route.extend(MyMixin, {\n        session: service(),\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n      })`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default Route.extend(MyMixin, {\n        router: service('router'),\nsession: service(),\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      })`,\n      errors: [\n        {\n          messageId: 'main',\n          data: { methodUsed: 'transitionTo', desiredMethod: 'transitionTo', moduleType: 'Route' },\n          type: 'MemberExpression',\n        },\n      ],\n    },\n\n    // Basic lint error in routes\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service('router') router;\n@service session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.replaceWith('login');\n          }\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service('router') router;\n@service session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.replaceWith('login');\n          }\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    // Basic lint error in controllers\n    {\n      filename: 'controllers/new-post.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController1 extends Controller {\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.transitionToRoute('post', post.id);\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController1 extends Controller {\n        @service('router') router;\n@action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.router.transitionTo('post', post.id);\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    {\n      filename: 'controllers/new-post.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController1 extends Controller {\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.replaceRoute('post', post.id);\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController1 extends Controller {\n        @service('router') router;\n@action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.router.replaceWith('post', post.id);\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n\n    // Existing router service\n    {\n      filename: 'controllers/new-post.js',\n      code: `\n      import { inject as service } from '@ember/service';\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController2 extends Controller {\n        @service('router') routerService;\n\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.transitionToRoute('post', post.id);\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController2 extends Controller {\n        @service('router') routerService;\n\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.routerService.transitionTo('post', post.id);\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    // Controller uses transitionToRoute with custom service injection import\n    {\n      filename: 'controllers/new-post.js',\n      code: `\n      import { inject as serviceInjection } from '@ember/service';\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController3 extends Controller {\n        @serviceInjection('router') routerService;\n\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.transitionToRoute('post', post.id);\n        }\n      }`,\n      output: `\n      import { inject as serviceInjection } from '@ember/service';\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class NewPostController3 extends Controller {\n        @serviceInjection('router') routerService;\n\n        @action\n        async save({ title, text }) {\n          let post = this.store.createRecord('post', { title, text });\n          await post.save();\n          return this.routerService.transitionTo('post', post.id);\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    // Existing injection name other than service\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as injectService } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @injectService session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as injectService } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @injectService('router') router;\n@injectService session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    // Injecting with `service` export\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { service } from '@ember/service';\n\n      export default class SettingsRoute extends Route {\n        @service('router') router;\n@service session;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    // Test multiple classes in a single file\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export class SettingsIndexRoute extends Route {\n        @service session;\n        @service('router') routerService;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n\n        model() {\n          return [];\n        }\n      }\n\n      export class SettingsDetailRoute extends Route {\n        @service('settings') settingsService;\n\n        async model(id) {\n          return new Setting(await this.settingsService.find(id));\n        }\n      }\n\n      export class SettingsRoute extends Route {\n        @service() router;\n        @service session;\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.transitionTo('login');\n          }\n        }\n      }`,\n\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export class SettingsIndexRoute extends Route {\n        @service session;\n        @service('router') routerService;\n\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.routerService.transitionTo('login');\n          }\n        }\n\n        model() {\n          return [];\n        }\n      }\n\n      export class SettingsDetailRoute extends Route {\n        @service('settings') settingsService;\n\n        async model(id) {\n          return new Setting(await this.settingsService.find(id));\n        }\n      }\n\n      export class SettingsRoute extends Route {\n        @service() router;\n        @service session;\n        beforeModel() {\n          if (!this.session.isAuthenticated) {\n            this.router.transitionTo('login');\n          }\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n\n    // Test rule in class returned by function\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      import { action } from '@ember/object';\n\n      export default function saver() {\n        return class NewPostController1 extends Controller {\n          @service('router') router;\n\n          @action\n          async save({ title, text }) {\n            let post = this.store.createRecord('post', { title, text });\n            await post.save();\n            return this.replaceRoute('post', post.id);\n          }\n        }\n      }`,\n      output: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      import { action } from '@ember/object';\n\n      export default function saver() {\n        return class NewPostController1 extends Controller {\n          @service('router') router;\n\n          @action\n          async save({ title, text }) {\n            let post = this.store.createRecord('post', { title, text });\n            await post.save();\n            return this.router.replaceWith('post', post.id);\n          }\n        }\n      }`,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'MemberExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-duplicate-dependent-keys.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-duplicate-dependent-keys');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE } = rule;\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\nruleTester.run('no-duplicate-dependent-keys', rule, {\n  valid: [\n    `\n      {\n        test: computed.match(\"email\", /^.+@.+/)\n      }\n      `,\n    `\n      {\n        foo: computed('model.foo', 'model.bar', 'model.baz', function() {})\n      }\n      `,\n    `\n      {\n        foo: computed('model.{foo,bar}', 'model.qux', function() {})\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('model.{foo,bar}', 'model.qux', function() {\n        }).volatile()\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('model.{foo,bar}', 'model.qux', 'collection.@each.fooProp', function() {\n        }).volatile()\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('model.{foo,bar}', 'model.qux', 'collection.[]', function() {\n        }).volatile()\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('model.{foo,bar}', 'model.qux', 'collection.@each.{foo,bar}', function() {\n        }).volatile()\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('collection.@each.{foo,bar}', 'collection.@each.qux', function() {\n        }).volatile()\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('collection.@each.foo', 'collection.@each.qux', function() {\n        }).volatile()\n      }\n      `,\n    `\n      {\n        foo: Ember.computed('collection.{foo.@each.prop, bar}', 'collection.foo.@each.qux', function() {\n        }).volatile()\n      }\n      `,\n  ].map(addComputedImport),\n  invalid: [\n    {\n      code: `\n      {\n        foo: computed('model.foo', 'model.bar', 'model.baz', 'model.foo', function() {})\n      }\n      `,\n      output: `\n      {\n        foo: computed('model.foo', 'model.bar', 'model.baz',  function() {})\n      }\n      `,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n      import Ember from 'ember';\n      { foo: Ember.computed('model.foo', 'model.bar', 'model.baz', 'model.foo', function() {}) }\n      `,\n      output: `\n      import Ember from 'ember';\n      { foo: Ember.computed('model.foo', 'model.bar', 'model.baz',  function() {}) }\n      `,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n      {\n        foo: computed('model.{foo,bar}', 'model.bar', function() {})\n      }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n      {\n        foo: computed('collection.@each.{foo,bar}', 'model.bar', 'collection.@each.bar', function() {})\n      }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n      {\n        foo: computed('collection.@each.foo', 'model.bar', 'collection.@each.foo', function() {})\n      }\n      `,\n      output: `\n      {\n        foo: computed('collection.@each.foo', 'model.bar',  function() {})\n      }\n      `,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n      {\n        foo: computed('collection.{foo.@each.qux,bar}', 'collection.foo.@each.qux', function() {})\n      }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n      {\n        foo: computed('a.b.c.{foo.@each.qux,bar}', 'a.b.c.baz.[]', 'a.b.c.foo.@each.qux', function() {})\n      }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: \"class Test { @computed('a', 'a') get someProp() { return true; } }\",\n      output: \"class Test { @computed('a' ) get someProp() { return true; } }\",\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/no-ember-super-in-es-classes.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-ember-super-in-es-classes');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022 },\n});\n\neslintTester.run('no-ember-super-in-es-classes', rule, {\n  valid: [\n    'EmberObject.extend({ init() { this._super(); } })',\n    'EmberObject.extend({ init(a, b) { this._super(a, b); } })',\n    'EmberObject.extend({ init() { this._super(...arguments); } })',\n    'class Foo { bar() { Baz.reopen({ quux() { this._super(); } }); } }',\n    'class Foo { bar() { return function() { this._super(); }; } }',\n    'class Foo { bar() { return { baz() { this._super() } }; } }',\n  ],\n  invalid: [\n    {\n      code: 'class Foo { init() { this._super(); } }',\n      output: 'class Foo { init() { super.init(); } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { init(a, b) { this._super(a); } }',\n      output: 'class Foo { init(a, b) { super.init(a); } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { init() { this._super(...arguments); } }',\n      output: 'class Foo { init() { super.init(...arguments); } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { init() { this._super.apply(this, arguments); } }',\n      output: 'class Foo { init() { super.init.apply(this, arguments); } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { init() { if (x) { this._super(1); } else { this._super(2); } } }',\n      output: 'class Foo { init() { if (x) { super.init(1); } else { super.init(2); } } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { \"a b\"() { this._super(); } }',\n      output: 'class Foo { \"a b\"() { super[\"a b\"](); } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { [Symbol.iterator]() { this._super(); } }',\n      output: 'class Foo { [Symbol.iterator]() { super[Symbol.iterator](); } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { init() { return { a: this._super() }; } }',\n      output: 'class Foo { init() { return { a: super.init() }; } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n    {\n      code: 'class Foo { init() { return () => { this._super(); }; } }',\n      output: 'class Foo { init() { return () => { super.init(); }; } }',\n      errors: [\n        { message: \"Don't use `this._super` in ES classes; instead, you should use `super`\" },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-ember-testing-in-module-scope.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-ember-testing-in-module-scope');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGES } = rule;\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-ember-testing-in-module-scope', rule, {\n  valid: [\n    `\n        import Ember from 'ember';\n\n        export default Ember.Component.extend({\n          someFunc() {\n            if (Ember.testing) {\n              doSomething();\n            } else {\n              doSomethingElse();\n            }\n          }\n        });\n      `,\n    `\n        import Ember from 'ember';\n\n        export default Ember.Component.extend({\n          someFunc() {\n            doSomething(Ember.testing ? 0 : 400);\n          }\n        });\n      `,\n    'foo.testing = true;',\n    'const { testing } = FooBar;',\n    'const testing = FooBar.testing',\n    'const testing = true;',\n  ],\n  invalid: [\n    {\n      code: `\n        import Random1 from 'random1';\n        import Ember from 'ember';\n        import Random2 from 'random2';\n\n        export default Ember.Component.extend({\n          init() {\n            this.isTesting = Ember.testing;\n          }\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGES[1] }],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        export default Ember.component.extend({\n          isTesting: Ember.testing\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGES[0] }],\n    },\n    {\n      code: `\n        import FooEmber from 'ember';\n\n        const testDelay = FooEmber.testing ? 0 : 400\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGES[0] }],\n    },\n    {\n      code: 'const IS_TESTING = Ember.testing;',\n      output: null,\n      errors: [{ message: ERROR_MESSAGES[1] }, { message: ERROR_MESSAGES[0] }],\n    },\n    {\n      code: 'const { testing } = Ember;',\n      output: null,\n      errors: [{ message: ERROR_MESSAGES[2] }],\n    },\n    {\n      code: `\n        import FooEmber from 'ember';\n\n        const { testing } = FooEmber;\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGES[2] }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-empty-attrs.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-empty-attrs');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n});\n\nconst message = 'Supply proper attribute type';\n\neslintTester.run('no-empty-attrs', rule, {\n  valid: [\n    'export default Model.extend();',\n    'export default Model.extend({name: attr(\"string\"), points: attr(\"number\"), dob: attr(\"date\")});',\n    'export default Model.extend({name: attr(\"string\")});',\n    {\n      code: `someArrayOfStrings.filter(function(attr) {\n        return attr.underscore();\n      });`,\n      parserOptions: { ecmaVersion: 2022 },\n    },\n    `export default Model.extend({\n        someArray: someArrayOfStrings.filter(function(attr) {\n          return attr.underscore();\n        }),\n      });`,\n    `import Model, { attr } from '@ember-data/model';\n      export default class UserModel extends Model {\n        @attr('string') name;\n      }`,\n  ],\n  invalid: [\n    {\n      code: `export default Model.extend({\n        name: attr(),\n        points: attr(\"number\"),\n        dob: attr(\"date\")\n      });`,\n      output: null,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      // With object variable.\n      code: 'const body = {name: attr()}; export default Model.extend(body);',\n      output: null,\n      errors: [{ message, line: 1 }],\n    },\n    {\n      code: `export default Model.extend({\n        name: attr(\"string\"),\n        points: attr(\"number\"),\n        dob: attr()\n      });`,\n      output: null,\n      errors: [{ message, line: 4 }],\n    },\n    {\n      code: `export default Model.extend({\n        name: attr(\"string\"),\n        points: attr(),\n        dob: attr(\"date\")\n      });`,\n      output: null,\n      errors: [{ message, line: 3 }],\n    },\n    {\n      code: `export default Model.extend({\n        name: attr(\"string\"),\n        points: attr(),\n        dob: attr(),\n        someComputedProperty: computed.bool(true)\n      });`,\n      output: null,\n      errors: [\n        { message, line: 3 },\n        { message, line: 4 },\n      ],\n    },\n    {\n      code: `export default Model.extend({\n        name: attr(),\n        points: attr(),\n        dob: attr()\n      });`,\n      output: null,\n      errors: [\n        { message, line: 2 },\n        { message, line: 3 },\n        { message, line: 4 },\n      ],\n    },\n    {\n      filename: 'example-app/models/some-model.js',\n      code: 'export default CustomModel.extend({name: attr()});',\n      output: null,\n      errors: [{ message, line: 1 }],\n    },\n    {\n      code: `import Model, { attr } from '@ember-data/model';\n        export default class UserModel extends Model {\n          @attr() name;\n        }`,\n      output: null,\n      errors: [{ message, line: 3 }],\n    },\n    {\n      code: `import Model, { attr } from '@ember-data/model';\n        export default class UserModel extends Model {\n          @attr name;\n        }`,\n      output: null,\n      errors: [{ message, line: 3 }],\n    },\n    {\n      code: `import Model, { attr } from '@ember-data/model';\n        export default (class UserModel extends Model {\n          @attr name;\n        });`,\n      output: null,\n      errors: [{ message, line: 3 }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-empty-glimmer-component-classes.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-empty-glimmer-component-classes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-empty-glimmer-component-classes', rule, {\n  valid: [\n    `import Component from '@glimmer/component';\n\n    class MyComponent extends Component {\n      foo() {\n        return this.args.bar + this.args.baz;\n      }\n    }`,\n    'class MyComponent extends NotAGlimmerComponent {}',\n    `import Component from '@glimmer/component';\n    import MyDecorator from 'my-decorator';\n\n    @MyDecorator\n    class MyComponent extends Component {}`,\n    `import Component from '@glimmer/component';\n    import MyDecorator from 'my-decorator';\n\n    @MyDecorator\n    class MyComponent extends Component {\n      <template>foo</template>\n    }`,\n    {\n      code: `\n      import Component from '@glimmer/component';\n\n      interface ListSignature<T> {\n        Args: {\n          items: Array<T>;\n        };\n        Blocks: {\n          default: [item: T]\n        };\n      }\n\n      export default class List<T> extends Component<ListSignature<T>> {}\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n    {\n      code: `\n        import Component from '@glimmer/component';\n\n        export default class MyComponent extends Component {\n          property = '';\n        }\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n    {\n      code: `\n        import Component from '@glimmer/component';\n\n        export interface SomeSig {}\n        export interface SomeOtherSig {}\n\n        export default class MyComponent<SomeSig> extends Component<SomeOtherSig> {}\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n    // TypeScript declare class should not be flagged\n    {\n      code: `\n        import Component from '@glimmer/component';\n\n        declare class MyComponent extends Component<Sig> {}\n      `,\n      parser: require.resolve('@typescript-eslint/parser'),\n    },\n  ],\n  invalid: [\n    {\n      code: `import Component from '@glimmer/component';\n\n      class MyComponent extends Component {}`,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ClassDeclaration' }],\n    },\n    {\n      code: `import Component from '@glimmer/component';\n\n      class MyComponent extends Component { /* foo */ }`,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ClassDeclaration' }],\n    },\n    {\n      code: `\n      import Component from '@glimmer/component';\n\n      export interface TypeSig {}\n\n      export default class MyComponent extends Component<TypeSig> {}\n      `,\n      output: null,\n      parser: require.resolve('@typescript-eslint/parser'),\n      errors: [{ message: ERROR_MESSAGE, type: 'ClassDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-function-prototype-extensions.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-function-prototype-extensions');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('no-function-prototype-extensions', rule, {\n  valid: [\n    'export default Controller.extend();',\n    'export default Controller.extend({actions: {}});',\n    'export default Controller.extend({test: function () {}});',\n    'export default Controller.extend({init() {}});',\n    'export default Controller.extend({test: computed(\"abc\", function () {})});',\n    'export default Controller.extend({test: observer(\"abc\", function () {})});',\n    'export default Controller.extend({test: beforeObserver(\"abc\", function () {})});',\n    'export default Controller.extend({test: service()});',\n    'export default Controller.extend({actions: {test() {}}});',\n    'export default Controller.extend({actions: {test: function () {}}});',\n    'export default Controller.extend({test: on(\"init\", function () {})});',\n    'export default Controller.extend({test: observer(\"abc\", function () {abc.on();})});',\n    'export default Controller.extend({test: beforeObserver(\"abc\", function () {abc.on();})});',\n    'export default Controller.extend({test: function () {$(\"body\").on(\"click\", abc);}});',\n    'export default Controller.extend({test() {$(\"body\").on(\"click\", abc);}});',\n    'export default Controller.extend({test() {$(\"body\").on(\"click\", abc).on(\"click\", function () {});}});',\n  ],\n  invalid: [\n    {\n      code: 'export default Controller.extend({test: function() {}.property(\"abc\")});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use Ember's function prototype extensions\",\n        },\n      ],\n    },\n    {\n      code: 'export default Controller.extend({test: function() {}.observes(\"abc\")});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use Ember's function prototype extensions\",\n        },\n      ],\n    },\n    {\n      code: 'export default Controller.extend({test: function() {}.observesBefore(\"abc\")});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use Ember's function prototype extensions\",\n        },\n      ],\n    },\n    {\n      code: 'export default Controller.extend({test: function() {}.on(\"init\")});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use Ember's function prototype extensions\",\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-get-with-default.js",
    "content": "const rule = require('../../../lib/rules/no-get-with-default');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-get-with-default', rule, {\n  valid: [\n    // get\n    \"this.get('key') || [];\",\n    \"import { get } from '@ember/object'; get(this, 'target') || [];\",\n\n    // getWithDefault\n    \"import { getWithDefault } from '@ember/object'; getWithDefault.testMethod(testClass, 'key', [])\",\n    \"getWithDefault(this, 'key', []);\", // Missing import\n\n    // With catchSafeObjects: false\n    {\n      code: \"import { getWithDefault } from '@ember/object'; getProperties('person', 'name', '');\",\n      options: [{ catchSafeObjects: false }],\n    },\n\n    // With catchUnsafeObjects: false\n    {\n      code: \"person.getWithDefault('name', '');\",\n      options: [{ catchUnsafeObjects: false }],\n    },\n  ],\n  invalid: [\n    // this.getWithDefault\n    {\n      code: \"const test = this.getWithDefault('key', []);\", // With a string property.\n      output: \"const test = (this.get('key') === undefined ? [] : this.get('key'));\",\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: 'const test = this.getWithDefault(SOME_VARIABLE, []);', // With a variable property.\n      output:\n        'const test = (this.get(SOME_VARIABLE) === undefined ? [] : this.get(SOME_VARIABLE));',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Having parenthesis around the autofix matters in this example.\n      code: \"this.getWithDefault('name', '').trim()\",\n      output: \"(this.get('name') === undefined ? '' : this.get('name')).trim()\",\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    // getWithDefault (imported)\n    {\n      code: \"import { getWithDefault } from '@ember/object'; getWithDefault(this, 'key', []);\", // With a string property.\n      output: `import { get } from '@ember/object';\nimport { getWithDefault } from '@ember/object'; (get(this, 'key') === undefined ? [] : get(this, 'key'));`,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // With renamed `getWithDefault` import:\n      code: \"import { getWithDefault as gwd } from '@ember/object'; gwd(this, 'key', []);\",\n      output: `import { get } from '@ember/object';\nimport { getWithDefault as gwd } from '@ember/object'; (get(this, 'key') === undefined ? [] : get(this, 'key'));`,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // With existing and renamed `get` import:\n      code: \"import { getWithDefault, get as g } from '@ember/object'; getWithDefault(this, 'key', []);\",\n      output:\n        \"import { getWithDefault, get as g } from '@ember/object'; (g(this, 'key') === undefined ? [] : g(this, 'key'));\",\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"import { getWithDefault } from '@ember/object'; getWithDefault(this, SOME_VARIABLE, []);\", // With a variable property.\n      output: `import { get } from '@ember/object';\nimport { getWithDefault } from '@ember/object'; (get(this, SOME_VARIABLE) === undefined ? [] : get(this, SOME_VARIABLE));`,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Having parenthesis around the autofix matters in this example.\n      code: \"import { getWithDefault } from '@ember/object'; getWithDefault(this, 'name', '').trim()\",\n      output: `import { get } from '@ember/object';\nimport { getWithDefault } from '@ember/object'; (get(this, 'name') === undefined ? '' : get(this, 'name')).trim()`,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    // With catchSafeObjects: true\n    {\n      code: \"import { getWithDefault } from '@ember/object'; getWithDefault(person, 'name', '');\",\n      output: `import { get } from '@ember/object';\nimport { getWithDefault } from '@ember/object'; (get(person, 'name') === undefined ? '' : get(person, 'name'));`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { getWithDefault } from '@ember/object'; getWithDefault(person, 'name', '');\",\n      output: `import { get } from '@ember/object';\nimport { getWithDefault } from '@ember/object'; (get(person, 'name') === undefined ? '' : get(person, 'name'));`,\n      options: [{ catchSafeObjects: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    // With catchUnsafeObjects: true (default)\n    {\n      code: \"person.getWithDefault('name', '');\",\n      output: \"(person.get('name') === undefined ? '' : person.get('name'));\",\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"person.getWithDefault('name', '');\",\n      output: \"(person.get('name') === undefined ? '' : person.get('name'));\",\n      options: [{ catchUnsafeObjects: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-get.js",
    "content": "const path = require('path');\nconst rule = require('../../../lib/rules/no-get');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE_GET, ERROR_MESSAGE_GET_PROPERTIES } = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-get', rule, {\n  valid: [\n    // **************************\n    // get\n    // **************************\n\n    // Nested property path.\n    {\n      code: \"this.get('foo.bar');\",\n      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],\n    },\n    {\n      code: \"import { get } from '@ember/object'; get(this, 'foo.bar');\",\n      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],\n    },\n\n    // Template literals.\n    {\n      code: 'this.get(`foo`);',\n      parserOptions: { ecmaVersion: 2022 },\n    },\n    {\n      code: \"import { get } from '@ember/object'; get(this, `foo`);\",\n      parserOptions: { ecmaVersion: 2022 },\n    },\n\n    // Not `this`.\n    \"foo.get('bar');\",\n    {\n      code: \"import { get } from '@ember/object'; get(foo, 'bar');\",\n      options: [{ catchSafeObjects: false }],\n    },\n\n    // Not `get`.\n    \"this.foo('bar');\",\n    \"foo(this, 'bar');\",\n\n    // Unknown extra argument.\n    \"this.get('foo', 'bar');\",\n    \"import { get } from '@ember/object'; get(this, 'foo', 'bar');\",\n\n    // Non-string, non-numerical parameter.\n    'this.get(MY_PROP);',\n    \"import { get } from '@ember/object'; get(this, MY_PROP);\",\n\n    // Unknown sub-function call:\n    \"this.get.foo('bar');\",\n    \"import { get } from '@ember/object'; get.foo(this, 'bar');\",\n\n    // In mirage directory\n    {\n      filename: path.join('app', 'mirage', 'config.js'),\n      code: 'this.get(\"/resources\")',\n    },\n\n    // Missing import:\n    \"get(this, 'foo');\",\n\n    // Ternary expressions with non-literal consequent or alternate\n    'this.get(foo ? bar : baz)',\n    `\n    import { get } from '@ember/object';\n    import { somethingElse } from '@ember/object';\n    import { random } from 'random';\n\n    const buzz = get(foo, bar ? baz : biz);\n    `,\n\n    // **************************\n    // getProperties\n    // **************************\n\n    // Nested property path.\n    {\n      code: \"this.getProperties('foo', 'bar.baz');\",\n      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],\n    },\n    {\n      code: \"this.getProperties(['foo', 'bar.baz']);\",\n      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],\n    }, // With parameters in array.\n    {\n      code: \"import { getProperties } from '@ember/object'; getProperties(this, 'foo', 'bar.baz');\",\n      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],\n    },\n    {\n      code: \"import { getProperties } from '@ember/object'; getProperties(this, ['foo', 'bar.baz']);\",\n      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],\n    }, // With parameters in array.\n\n    // Template literals.\n    'this.getProperties(`prop1`, `prop2`);',\n    \"import { getProperties } from '@ember/object'; getProperties(this, `prop1`, `prop2`);\",\n\n    // Not `this`.\n    \"myObject.getProperties('prop1', 'prop2');\",\n    {\n      code: \"import { getProperties } from '@ember/object'; getProperties(myObject, 'prop1', 'prop2');\",\n      options: [{ catchSafeObjects: false }],\n    },\n\n    // Not `getProperties`.\n    \"this.foo('prop1', 'prop2');\",\n\n    // Non-string parameter.\n    'this.getProperties(MY_PROP);',\n    'this.getProperties(...MY_PROPS);',\n    'this.getProperties([MY_PROP]);',\n    \"import { getProperties } from '@ember/object'; getProperties(this, MY_PROP);\",\n    \"import { getProperties } from '@ember/object'; getProperties(this, ...MY_PROPS);\",\n    \"import { getProperties } from '@ember/object'; getProperties(this, [MY_PROP]);\",\n\n    // Unknown sub-function call:\n    \"this.getProperties.foo('prop1', 'prop2');\",\n\n    // Missing import:\n    \"getProperties(this, 'prop1', 'prop2');\",\n\n    // With ignoreGetProperties: true\n    {\n      code: \"this.getProperties('prop1', 'prop2');\",\n      options: [{ ignoreGetProperties: true }],\n    },\n    {\n      code: \"this.getProperties(['prop1', 'prop2']);\", // With parameters in array.\n      options: [{ ignoreGetProperties: true }],\n    },\n    {\n      code: \"import { getProperties } from '@ember/object'; getProperties(this, 'prop1', 'prop2');\",\n      options: [{ ignoreGetProperties: true }],\n    },\n    {\n      code: \"import { getProperties } from '@ember/object'; getProperties(this, ['prop1', 'prop2']);\", // With parameters in array.\n      options: [{ ignoreGetProperties: true }],\n    },\n\n    // Ignores `get()` inside proxy objects (which still require using `get()`):\n    `\n    import ObjectProxy from '@ember/object/proxy';\n    export default ObjectProxy.extend({\n      someFunction() {\n        test();\n        console.log(this.get('propertyInsideProxyObject'));\n      }\n    });\n    `,\n    `\n    import ArrayProxy from '@ember/array/proxy';\n    export default ArrayProxy.extend({\n      someFunction() {\n        test();\n        console.log(this.get('propertyInsideProxyObject'));\n      }\n    });\n    `,\n    `\n    import ArrayProxy from '@ember/array/proxy';\n    class MyProxy extends ArrayProxy {\n      someFunction() {\n        test();\n        console.log(this.get('propertyInsideProxyObject'));\n      }\n    }\n    `,\n    `\n    import ArrayProxy from '@ember/array/proxy';\n    class MyProxy extends ArrayProxy.extend(SomeMixin) {\n      someFunction() {\n        test();\n        console.log(this.get('propertyInsideProxyObject'));\n      }\n    }\n    `,\n\n    // Ignores `get()` inside classes with `unknownProperty`:\n    `\n    import EmberObject from '@ember/object';\n    export default EmberObject.extend({\n      unknownProperty() {},\n      someFunction() {\n        console.log(this.get('propertyInsideClassWithUnknownProperty'));\n      }\n    });\n    `,\n    `\n    import EmberObject from '@ember/object';\n    class MyClass extends EmberObject {\n      unknownProperty() {}\n      someFunction() {\n        console.log(this.get('propertyInsideClassWithUnknownProperty'));\n      }\n    }\n    `,\n\n    // Optional chaining:\n    'this.foo?.bar',\n    'this.foo?.[0]?.bar',\n  ],\n  invalid: [\n    // **************************\n    // get\n    // **************************\n\n    {\n      code: \"this.get('foo');\",\n      output: 'this.foo;',\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: \"foo1.foo2.get('bar');\",\n      output: 'foo1.foo2.bar;',\n      options: [{ catchUnsafeObjects: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: \"foo1.foo2.get('bar').baz;\",\n      output: 'foo1.foo2.bar.baz;',\n      options: [{ catchUnsafeObjects: true, useOptionalChaining: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // useOptionalChaining = false\n      // We can safely autofix nested paths when the result of get() is chained,\n      code: \"foo1.foo2.get('bar.bar').baz;\",\n      output: 'foo1.foo2.bar.bar.baz;',\n      options: [{ catchUnsafeObjects: true, useOptionalChaining: false }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // useOptionalChaining = true (implicit)\n      code: \"foo1.foo2.get('bar.bar').baz;\",\n      output: 'foo1.foo2.bar.bar.baz;',\n      options: [{ catchUnsafeObjects: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // useOptionalChaining = true (explicit)\n      code: \"foo1.foo2.get('bar.bar').baz;\",\n      output: 'foo1.foo2.bar.bar.baz;',\n      options: [{ catchUnsafeObjects: true, useOptionalChaining: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      get(this, 'foo');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      this.foo;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // Calling the imported function on an unknown object (without `this`).\n      code: \"import { get } from '@ember/object'; get(foo1.foo2, 'bar');\",\n      output: \"import { get } from '@ember/object'; foo1.foo2.bar;\",\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // Calling the imported function on an unknown object (without `this`) with an object argument that needs parenthesis.\n      code: \"import { get } from '@ember/object'; get(foo || {}, 'bar');\",\n      output: \"import { get } from '@ember/object'; (foo || {}).bar;\",\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // With renamed import:\n      code: \"import { get as g } from '@ember/object'; g(this, 'foo');\",\n      output: \"import { get as g } from '@ember/object'; this.foo;\",\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"this.get('foo').someFunction();\",\n      output: 'this.foo.someFunction();',\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // With invalid JS variable name:\n      code: \"this.get('foo-bar');\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // With invalid JS variable name:\n      code: \"import { get } from '@ember/object'; get(this, 'foo-bar');\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    // **************************\n    // getProperties\n    // **************************\n\n    {\n      code: \"let obj = this.getProperties('prop1', 'prop2');\",\n      output: 'let obj = { prop1: this.prop1, prop2: this.prop2 };',\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"const baz = foo.getProperties('prop1', 'prop2');\",\n      output: 'const baz = { prop1: foo.prop1, prop2: foo.prop2 };',\n      options: [{ catchUnsafeObjects: true }],\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"const obj = this.getProperties(['prop1', 'prop2']);\", // With parameters in array.\n      output: 'const obj = { prop1: this.prop1, prop2: this.prop2 };',\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"const obj = this.getProperties('1');\", // With invalid JS variable name.\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"const obj = this.getProperties('a*');\", // With invalid JS variable name.\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"const obj = this.getProperties('obj.foo');\", // With invalid JS variable name.\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { getProperties } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      const obj = getProperties(this, 'prop1', 'prop2');\n      `,\n      output: `\n      import { getProperties } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      const obj = { prop1: this.prop1, prop2: this.prop2 };\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { getProperties } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      function foo(){\n        return getProperties(this, 'prop1', 'prop2');\n      }\n      `,\n      output: `\n      import { getProperties } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      function foo(){\n        return { prop1: this.prop1, prop2: this.prop2 };\n      }\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      // Calling the imported function on an unknown object (without `this`).\n      code: \"import { getProperties } from '@ember/object'; let obj = getProperties(foo, 'prop1', 'prop2');\",\n      output:\n        \"import { getProperties } from '@ember/object'; let obj = { prop1: foo.prop1, prop2: foo.prop2 };\",\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      // With renamed import:\n      code: \"import { getProperties as gp } from '@ember/object'; const obj = gp(this, 'prop1', 'prop2');\",\n      output:\n        \"import { getProperties as gp } from '@ember/object'; const obj = { prop1: this.prop1, prop2: this.prop2 };\",\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { getProperties } from '@ember/object'; const obj = getProperties(this, ['prop1', 'prop2']);\", // With parameters in array.\n      output:\n        \"import { getProperties } from '@ember/object'; const obj = { prop1: this.prop1, prop2: this.prop2 };\",\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { getProperties } from '@ember/object';\n      const { foo, bar } = getProperties(\n        this.obj,\n        \"foo\",\n        \"bar\"\n      );\n      `,\n      output: `\n      import { getProperties } from '@ember/object';\n      const { foo, bar } = this.obj;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { getProperties } from '@ember/object';\n      const { foo: qux, bar } = getProperties(\n        this.obj,\n        \"bar\",\n        \"foo\"\n      );\n      `,\n      output: `\n      import { getProperties } from '@ember/object';\n      const { foo: qux, bar } = this.obj;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { getProperties } from '@ember/object';\n      const { foo, bar, ...qux } = getProperties(\n        this.obj,\n        \"foo\",\n        \"bar\",\n        \"baz\",\n        \"frex\"\n      );\n      `,\n      output: `\n      import { getProperties } from '@ember/object';\n      const { foo, bar, ...qux } = this.obj;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { getProperties } from '@ember/object';\n\n      const { foo, bar, baz } = getProperties(\n        get(obj, 't.s'),\n        'foo',\n        'bar',\n        'baz',\n      );\n      `,\n      output: `\n      import { getProperties } from '@ember/object';\n\n      const { foo, bar, baz } = get(obj, 't.s');\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n\n    // Nested paths:\n    {\n      code: \"this.get('foo.bar');\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"import { get } from '@ember/object'; get(this, 'foo.bar');\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"this.getProperties('foo.bar');\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { getProperties } from '@ember/object'; getProperties(this, 'foo.bar');\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_GET_PROPERTIES, type: 'CallExpression' }],\n    },\n\n    // Nested paths with optional chaining:\n    {\n      code: \"this.get('foo.bar');\",\n      output: 'this.foo?.bar;',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"this.get('foo.bar').baz;\",\n      output: 'this.foo.bar.baz;',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"this.get('very.long.path');\",\n      output: 'this.very?.long?.path;',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"import { get } from '@ember/object'; get(this, 'foo.bar');\",\n      output: \"import { get } from '@ember/object'; this.foo?.bar;\",\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"import { get } from '@ember/object'; get(this, 'very.long.path');\",\n      output: \"import { get } from '@ember/object'; this.very?.long?.path;\",\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"this.get('foo');\", // No nested path.\n      output: 'this.foo;',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Optional chaining is not valid in the left side of an assignment,\n      // and we can safely autofix nested paths without it anyway.\n      code: \"this.get('foo.bar')[123] = 'hello world';\",\n      output: \"this.foo.bar[123] = 'hello world';\",\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Handle array element access with optional chaining (beginning/middle/end of string).\n      code: \"this.get('0.foo1.1.2.bar1bar.3')\",\n      output: 'this[0]?.foo1?.[1]?.[2]?.bar1bar?.[3]',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Handle array element access as entire string.\n      code: \"this.get('0')\",\n      output: 'this[0]',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Handle array element access (left side of an assignment, beginning/middle/end of string).\n      code: \"this.get('0.foo.1.bar.2')[123] = 'hello world';\",\n      output: \"this[0].foo[1].bar[2][123] = 'hello world';\",\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // We can safely autofix nested paths when the result of get() is chained,\n      code: \"this.get('foo.0.bar')[123];\",\n      output: 'this.foo[0].bar[123];',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Handle array element access (left side of an assignment, entire string).\n      code: \"this.get('0')[123] = 'hello world';\",\n      output: \"this[0][123] = 'hello world';\",\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // We can safely autofix nested paths in the left side of an assignment,\n      // even when the `useOptionalChaining` option is off.\n      code: \"this.get('foo.bar')[123] = 'hello world';\",\n      output: \"this.foo.bar[123] = 'hello world';\",\n      options: [{ useOptionalChaining: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      get(this, 'foo.firstObject.bar');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      this.foo?.[0]?.bar;\n      `,\n      options: [{ useOptionalChaining: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { get as g } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      g(obj.baz.qux, 'foo.firstObject.bar');\n      `,\n      output: `\n      import { get as g } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      obj.baz.qux.foo?.[0]?.bar;\n      `,\n      options: [{ useOptionalChaining: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // `firstObject` used in the middle of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('foo.firstObject.bar')[123];\",\n      output: 'this.foo[0].bar[123];',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `firstObject` used in multiple places in a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('firstObject.foo.firstObject.bar')[123];\",\n      output: 'this[0].foo[0].bar[123];',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `firstObject` used in the middle of a path.\n      // And the resolved path of `get` is NOT chained (getResultIsChained=false).\n      code: \"this.get('foo.firstObject.bar');\",\n      output: 'this.foo?.[0]?.bar;',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `firstObject` used at the beginning of a path.\n      // And the resolved path of `get` is NOT chained (getResultIsChained=false).\n      code: \"this.get('firstObject.bar');\",\n      output: 'this[0]?.bar;',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `firstObject` used as the entire path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('firstObject')[123];\",\n      output: 'this[0][123];',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `firstObject` used in the middle of a path.\n      // And `get` is used in a left side of an assignment (isInLeftSideOfAssignmentExpression=true).\n      code: \"this.get('foo.firstObject.bar')[123] = 'hello world';\",\n      output: \"this.foo[0].bar[123] = 'hello world';\",\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `lastObject` used in the middle of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('foo.lastObject.bar')[123];\",\n      output: 'this.foo.at(-1).bar[123];',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // useAt default value\n      // `lastObject` used at the beginning of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('lastObject.bar')[123];\",\n      output: 'this.at(-1).bar[123];',\n      options: [{ useOptionalChaining: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `lastObject` used at the beginning of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('lastObject.bar')[123];\",\n      output: 'this.at(-1).bar[123];',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `lastObject` used as the entire path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('lastObject')[123];\",\n      output: 'this.at(-1)[123];',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `lastObject` used in the middle of a path.\n      // And the resolved path of `get` is NOT chained (getResultIsChained=false).\n      code: \"this.get('foo.lastObject.bar');\",\n      output: 'this.foo?.at(-1)?.bar;',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `lastObject` used at the beginning of a path.\n      // And the resolved path of `get` is NOT chained (getResultIsChained=false).\n      code: \"this.get('lastObject.bar');\",\n      output: 'this.at(-1)?.bar;',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // multiple `lastObject` used in the middle of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('foo.lastObject.bar.lastObject')[123];\",\n      output: 'this.foo.at(-1).bar.at(-1)[123];',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // `lastObject` used at the beginning of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('lastObject.bar.lastObject')[123];\",\n      output: 'this.at(-1).bar.at(-1)[123];',\n      options: [{ useOptionalChaining: true, useAt: true }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    {\n      // useAt = false.\n      // `lastObject` used at the beginning of a path.\n      // And the resolved path of `get` is NOT chained (getResultIsChained=false).\n      code: \"this.get('lastObject.bar');\",\n      output: 'this[this.length - 1]?.bar;',\n      options: [{ useOptionalChaining: true, useAt: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // useAt = false.\n      // `lastObject` used as the entire path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('lastObject')[123];\",\n      output: 'this[this.length - 1][123];',\n      options: [{ useOptionalChaining: true, useAt: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      //  useAt = false.\n      // multiple `lastObject` used in the middle of a path.\n      // And the result of get() is chained (getResultIsChained=true).\n      code: \"this.get('foo.lastObject.bar.lastObject')[123];\",\n      output: null,\n      options: [{ useOptionalChaining: true, useAt: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      //  useAt = false.\n      // `lastObject` used in the middle of a path.\n      // And the resolved path of `get` is NOT chained (getResultIsChained=false).\n      code: \"this.get('foo.lastObject.bar');\",\n      output: 'this.foo?.[this.foo.length - 1]?.bar;',\n      options: [{ useOptionalChaining: true, useAt: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_GET,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Reports violation after (classic) proxy class.\n      code: `\n      import ArrayProxy from '@ember/array/proxy';\n      export default ArrayProxy.extend({\n        someFunction() {\n          test();\n          console.log(this.get('propertyInsideProxyObject'));\n        }\n      });\n      this.get('propertyOutsideClass');\n      `,\n      output: `\n      import ArrayProxy from '@ember/array/proxy';\n      export default ArrayProxy.extend({\n        someFunction() {\n          test();\n          console.log(this.get('propertyInsideProxyObject'));\n        }\n      });\n      this.propertyOutsideClass;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // Reports violation after (native) proxy class.\n      code: `\n      import ArrayProxy from '@ember/array/proxy';\n      class MyProxy extends ArrayProxy {\n        someFunction() {\n          test();\n          console.log(this.get('propertyInsideProxyObject'));\n        }\n      }\n      this.get('propertyOutsideClass');\n      `,\n      output: `\n      import ArrayProxy from '@ember/array/proxy';\n      class MyProxy extends ArrayProxy {\n        someFunction() {\n          test();\n          console.log(this.get('propertyInsideProxyObject'));\n        }\n      }\n      this.propertyOutsideClass;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n\n    {\n      // Reports violation after (classic) class with `unknownProperty()`.\n      code: `\n      import EmberObject from '@ember/object';\n      export default EmberObject.extend({\n        unknownProperty() {},\n        someFunction() {\n          console.log(this.get('propertyInsideClassWithUnknownProperty'));\n        }\n      });\n      this.get('propertyOutsideClass');\n      `,\n      output: `\n      import EmberObject from '@ember/object';\n      export default EmberObject.extend({\n        unknownProperty() {},\n        someFunction() {\n          console.log(this.get('propertyInsideClassWithUnknownProperty'));\n        }\n      });\n      this.propertyOutsideClass;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      // Reports violation after (native) class with `unknownProperty()`.\n      code: `\n      import EmberObject from '@ember/object';\n      class MyClass extends EmberObject {\n        unknownProperty() {}\n        someFunction() {\n          console.log(this.get('propertyInsideClassWithUnknownProperty'));\n        }\n      }\n      this.get('propertyOutsideClass');\n      `,\n      output: `\n      import EmberObject from '@ember/object';\n      class MyClass extends EmberObject {\n        unknownProperty() {}\n        someFunction() {\n          console.log(this.get('propertyInsideClassWithUnknownProperty'));\n        }\n      }\n      this.propertyOutsideClass;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    // Accessing numerical property with get\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      get(foo, 5);\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n      foo[5];\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: 'this.get(5);',\n      output: 'this[5];',\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    // Logical and conditional expressions\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const bar = baz ? get(foo, 'biz') : get(foo, 'buzz');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const bar = baz ? foo.biz : foo.buzz;\n      `,\n      errors: [\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n      ],\n    },\n    {\n      code: \"foo ? this.get('bar') : this.get('baz')\",\n      output: 'foo ? this.bar : this.baz',\n      errors: [\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n      ],\n    },\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const bar = get(foo, 'biz') || get(foo, 'buzz');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const bar = foo.biz || foo.buzz;\n      `,\n      errors: [\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n      ],\n    },\n    {\n      code: \"this.get('bar') || this.get('baz')\",\n      output: 'this.bar || this.baz',\n      errors: [\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n        { message: ERROR_MESSAGE_GET, type: 'CallExpression' },\n      ],\n    },\n    // Ternary expressions with literal consequent and alternate\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const buzz = get(foo, bar ? 'biz' : 'baz');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const buzz = bar ? foo.biz : foo.baz;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: \"this.get(foo ? 'bar' : 'baz')\",\n      output: 'foo ? this.bar : this.baz',\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const buzz = get(foo, bar ? 5 : 'baz');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const buzz = bar ? foo[5] : foo.baz;\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: \"this.get(foo ? 5 : 'baz')\",\n      output: 'foo ? this[5] : this.baz',\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    // Ternary expressions with literal consequent and alternate with optional chaining\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const buzz = get(foo, bar ? 'biz.baz' : 'baz.biz');\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      const buzz = bar ? foo.biz?.baz : foo.baz?.biz;\n      `,\n      options: [{ useOptionalChaining: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: \"this.get(foo ? 'bar.baz' : 'baz.bar')\",\n      output: 'foo ? this.bar?.baz : this.baz?.bar',\n      options: [{ useOptionalChaining: true }],\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    // Ternary expressions with left-hand side of assignment expression\n    {\n      code: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      get(foo, bar ? 'biz' : 'baz').buzz = 'something';\n      `,\n      output: `\n      import { get } from '@ember/object';\n      import { somethingElse } from '@ember/object';\n      import { random } from 'random';\n\n      (bar ? foo.biz : foo.baz).buzz = 'something';\n      `,\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n    {\n      code: \"this.get(foo ? 'bar' : 'baz').buzz = 'something';\",\n      output: \"(foo ? this.bar : this.baz).buzz = 'something';\",\n      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-global-jquery.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-global-jquery');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester();\nconst parserOptions = {\n  ecmaVersion: 2022,\n  sourceType: 'module',\n};\nconst globals = { $: true, jQuery: true };\n\nconst { ERROR_MESSAGE } = rule;\n\nruleTester.run('no-global-jquery', rule, {\n  valid: [\n    {\n      code: `\n        import $ from 'jquery';\n\n        export default Ember.Component.extend({\n          init() {\n            this.el = $('.test');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import $ from 'jquery';\n\n        const foo = $;\n\n        export default Ember.Component.extend({\n          init() {\n            this.el = foo('.gangnam-style');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        import Foo from 'bar';\n\n        export default Ember.Component.extend({\n          init() {\n            this.el = Ember.$('.lololol');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const { $, Component } = Ember;\n        const { eq } = Ember.computed;\n\n        export default Component.extend({\n          init() {\n            this.el = $('.baba-booey');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        import Foo from 'bar';\n\n        const { $ } = Ember;\n        const { foo } = Foo;\n\n        export default Ember.Component.extend({\n          init() {\n            this.el = $('.to-foo-or-not-to-foo');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        import Foo from 'bar';\n\n        const { $ } = Ember;\n        const { baz } = Foo.bar;\n\n        export default Ember.Component.extend({\n          init() {\n            this.el = $('.homie-dont-play-that');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const { $ } = Ember;\n        const wut = 'lolerskates';\n\n        export default Ember.Controller.extend({\n          init() {\n            this.el = $('.whyowhyowhy');\n          }\n        });\n      `,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        let something;\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              something = Ember.$('.invalid1');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const Em = Ember;\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              this.inv1 = Em.$('.invalid1');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        export default Ember.Component({\n          valid1() {\n            this.v1 = Ember.$('.v1');\n          },\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        export default Ember.Component({\n          valid2() {\n            this.v2 = this.$();\n          },\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        export default Ember.Component({\n          actions: {\n            valid3() {\n              this.v3 = Ember.$('v3');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        export default Ember.Component({\n          actions: {\n            valid4() {\n              this.v4 = this.$('v4');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const { $ } = Ember;\n\n        export default Ember.Component({\n          init() {\n            this.el = $('.test');\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const { $ } = Ember;\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              this.inv1 = $('.invalid1');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const { $: foo } = Ember;\n\n        export default Ember.Component({\n          init() {\n            this.el = foo('.test');\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const { $: foo } = Ember;\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              this.inv1 = foo('.invalid1');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const $ = Ember.$;\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              this.valid = $('.valid');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const foo = Ember.$;\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              this.valid = foo('.valid');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      code: `\n        import $ from 'jquery';\n        import Ember from 'ember';\n\n        export default Ember.Component({\n          actions: {\n            valid() {\n              this.valid = $('.valid');\n            }\n          }\n        });`,\n      parserOptions,\n      globals,\n    },\n    {\n      // Function parameter:\n      code: `\n      function addClass($, selector, className) {\n        $(selector).addClass(className);\n      }`,\n      parserOptions,\n      globals,\n    },\n    {\n      // From another object:\n      code: 'Foo.$(selector).attr(attribute);',\n      parserOptions,\n      globals,\n    },\n    {\n      // From another object:\n      code: `\n      const { $ } = Foo;\n      $(selector).attr(attribute);`,\n      parserOptions,\n      globals,\n    },\n    {\n      // Without globals:\n      code: `\n        $('foo');\n        jQuery('foo');`,\n      parserOptions,\n    },\n  ],\n  invalid: [\n    {\n      code: `\n        export default Ember.Component({\n          init() {\n            this.el = $('.test');\n          }\n        });`,\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        export default Ember.Component({\n          actions: {\n            invalid1() {\n              this.inv1 = $('.invalid1');\n            }\n          }\n        });`,\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        export default Ember.Component({\n          init() {\n            this.el = jQuery('.test');\n          }\n        });`,\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        export default Ember.Component({\n          actions: {\n            invalid1() {\n              this.inv1 = jQuery('.invalid1');\n            }\n          }\n        });`,\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const foo = Ember.$;\n\n        export default Ember.Component({\n          actions: {\n            invalid1() {\n              this.inv1 = $('.invalid1');\n            }\n          }\n        });`,\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n\n        const foo = Ember.$;\n\n        export default Ember.Component({\n          actions: {\n            invalid1() {\n              this.inv1 = jQuery('.invalid1');\n            }\n          }\n        });`,\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: 'jQuery.extend();',\n      output: null,\n      parserOptions,\n      globals,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-html-safe.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-html-safe');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-html-safe', rule, {\n  valid: [\n    // Wrong function:\n    \"import { ANYTHING } from '@ember/string'; ANYTHING('foo');\",\n    \"import { ANYTHING } from '@ember/template'; ANYTHING('foo');\",\n\n    // Wrong import path:\n    \"import { htmlSafe } from 'something/else'; htmlSafe('foo');\",\n\n    // No import:\n    \"htmlSafe('foo');\",\n    \"import EmberString from '@ember/string'; htmlSafe('foo');\",\n\n    // Wrong object:\n    \"import { htmlSafe } from '@ember/string'; foo.htmlSafe('foo');\",\n    \"import { htmlSafe } from '@ember/string'; htmlSafe.foo('foo');\",\n  ],\n  invalid: [\n    {\n      code: \"import { htmlSafe } from '@ember/string'; htmlSafe('foo');\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { htmlSafe } from '@ember/template'; htmlSafe('foo');\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"import { htmlSafe as myCustomNameForHtmlSafe } from '@ember/template'; myCustomNameForHtmlSafe();\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-implicit-injections.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-implicit-injections');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n});\n\nconst FLASH_MESSAGES_CONFIG = {\n  denyList: [{ service: 'flash-messages' }],\n};\nconst MEDIA_CONFIG = {\n  denyList: [{ service: 'media', moduleNames: ['Component', 'Controller'] }],\n};\nconst FEATURE_CHECKER_CONFIG = {\n  denyList: [{ service: 'feature', propertyName: 'featureChecker' }],\n};\nconst NESTED_SERVICE_CONFIG = {\n  denyList: [{ service: 'cart/checkout', propertyName: 'checkout' }],\n};\n\nfunction createClassUsage(serviceDefinition) {\n  return {\n    filename: 'pods/index.js',\n    code: `\n    import { inject as service } from '@ember/service';\n    import Component from '@ember/component';\n\n    export default class FoobarTestError extends Component {\n      ${serviceDefinition}\n\n      @action\n      save() {\n        return this.flashMessages.warn('some message');\n      }\n    }`,\n    options: [FLASH_MESSAGES_CONFIG],\n  };\n}\n\nfunction createExtendUsage(serviceDefinition) {\n  return {\n    filename: 'pods/index.js',\n    code: `\n    import { inject as service } from '@ember/service';\n    import Component from '@ember/component';\n\n    export default Component.extend({\n      ${serviceDefinition}\n\n      actions: {\n\n        save() {\n          return this.flashMessages.warn('some message');\n        }\n      }\n    });`,\n    options: [FLASH_MESSAGES_CONFIG],\n  };\n}\n\nruleTester.run('no-implicit-injections', rule, {\n  valid: [\n    // Basic use in Route and Controller in single file\n    {\n      filename: 'pods/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class IndexController extends Controller {\n        @service('store') store;\n        async loadData() {\n          return this.store.findAll('rental');\n        }\n      }\n\n      export class IndexRoute extends Route {\n        @service('store') store;\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n    },\n    // Basic use in Controller\n    {\n      filename: 'controller/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n      import { inject as service } from '@ember/service';\n\n      export default class IndexController extends Controller {\n        @service('store') store;\n        @action\n        async loadUsers() {\n          return this.store.findAll('user');\n        }\n      }`,\n    },\n    // Ignores if some other property getter is defined\n    {\n      filename: 'controller/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n      import { inject as service } from '@ember/service';\n      import storeMock from './my-mock';\n\n      export default class IndexController extends Controller {\n        store = storeMock;\n\n        @action\n        async loadUsers() {\n          return this.store.findAll('user');\n        }\n      }`,\n    },\n    // Ignores if some other property getter is defined\n    {\n      filename: 'controller/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n      import { inject as service } from '@ember/service';\n\n      export default class IndexController extends Controller {\n        get store() {\n          return {}\n        }\n        @action\n        async loadUsers() {\n          return this.store.findAll('user');\n        }\n      }`,\n    },\n    // Only checks for ThisExpression\n    {\n      filename: 'controller/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class IndexController extends Controller {\n        @action\n        async loadUsers(arg) {\n          return arg.store.findAll('user');\n        }\n      }`,\n    },\n    // Does not check for Store use in Components\n    {\n      filename: 'components/foobar.js',\n      code: `\n      import Component from '@ember/component';\n\n      export default class FoobarTest extends Component {\n        async model() {\n          return this.store.isXs;\n        }\n      }`,\n    },\n    // Does not check for Store use in GlimmerComponents\n    {\n      filename: 'components/foobar.js',\n      code: `\n      import Component from '@glimmer/component';\n\n      export default class FoobarTest extends Component {\n        async model() {\n          return this.store.isXs;\n        }\n      }`,\n    },\n    // Checks custom config\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n\n      export default class IndexController extends Controller {\n        @service('media') media;\n\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }`,\n      options: [MEDIA_CONFIG],\n    },\n    // Ignores use when property name is modified from 1:1 mapping\n    {\n      filename: 'controllers/index.js',\n      code: `\n      import Controller from '@ember/controller';\n\n      export default class IndexController extends Controller {\n        get isSmallScreen() {\n          return this.feature.isXs;\n        }\n      }`,\n      options: [FEATURE_CHECKER_CONFIG],\n    },\n    // Can detect changed property mapping\n    {\n      filename: 'routes/index.js',\n      code: `\n      import { inject as service } from '@ember/service';\n      import Route from '@ember/routing/route';\n\n      export default class IndexRoute extends Route {\n        @service('feature') featureChecker;\n\n        get canVisitCheckout() {\n          return this.featureChecker.isEnabled('checkout');\n        }\n      }`,\n      options: [FEATURE_CHECKER_CONFIG],\n    },\n    // Can work with services with nested module paths\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class IndexRoute extends Route {\n        @service('cart/checkout') checkout;\n\n        model() {\n          return this.checkout.viewCart();\n        }\n      }`,\n      options: [NESTED_SERVICE_CONFIG],\n    },\n    // Ignores use outside of classes\n    {\n      filename: 'utils/support.js',\n      code: `\n      export function checkMedia() {\n        return this.media.isXs;\n      }`,\n      options: [\n        {\n          denyList: [{ service: 'media' }],\n        },\n      ],\n    },\n    // Custom Configs work with multiple class/module types both matching and not\n    {\n      filename: 'pods/user.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class User {\n        get mediaAccount() {\n          return this.media.account;\n        }\n      }\n\n      export class UserController extends Controller {\n        @service('media') media;\n\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }\n\n      export class UserRoute extends Route {\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }`,\n      options: [MEDIA_CONFIG],\n    },\n\n    // Reassesses Module Type for Nested Classes\n    {\n      filename: 'controllers/register.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class RegisterController extends Controller {\n        @service('store') store;\n        async loadData() {\n          return this.store.findAll('rental');\n        }\n\n        getMediaUser() {\n          return class Register {\n            get storeInfo() {\n              return this.store.address;\n            }\n          }\n        }\n      }`,\n    },\n\n    // Nested Ember Module Definition (used in some meta programming instances or decorators)\n    {\n      filename: 'utils/loads-user-controller.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export function mediaAwareRoute() {\n        return class UserController extends Controller {\n          @inject('store') store;\n          async loadData() {\n            return this.store.findAll('rental');\n          }\n        }\n      }`,\n    },\n\n    // Exhaustive check for property definition type\n    createClassUsage('@service flashMessages'),\n    createClassUsage('@service() flashMessages'),\n    createClassUsage(\"@service('flashMessages') flashMessages\"),\n    createClassUsage(\"@service('flash-messages') flashMessages\"),\n\n    createExtendUsage('flashMessages: service(),'),\n    createExtendUsage(\"flashMessages: service('flashMessages'),\"),\n    createExtendUsage(\"flashMessages: service('flash-messages'),\"),\n\n    // Does not error on .create\n    {\n      filename: 'utils/loads-user-controller.js',\n      code: `\n      import EmberObject from '@ember/object';\n\n      const myObj = EmberObject.create();`,\n    },\n\n    // Does not error on empty .extend\n    {\n      filename: 'utils/loads-user-controller.js',\n      code: `\n      import EmberObject from '@ember/object';\n\n      const myObj = EmberObject.extend()`,\n    },\n\n    // Does not error when using mixin with native class (common for validations)\n    {\n      filename: 'controller-mixin/index.js',\n      code: `\n      import { inject as service } from '@ember/service';\n      import Component from '@ember/component';\n      import SomeMixin from './my-mixin';\n\n      export default class FoobarTestError extends Component.extend(SomeMixin) {\n        @service flashMessages;\n\n        @action\n        save() {\n          return this.flashMessages.warn('some message');\n        }\n      }`,\n      options: [FLASH_MESSAGES_CONFIG],\n    },\n\n    // Does not error when using Mixin\n    {\n      filename: 'controller/index.js',\n      code: `\n      import { inject as service } from '@ember/service';\n      import Component from '@ember/component';\n      import SomeMixin from './my-mixin';\n\n      export default Component.extend(SomeMixin, {\n        flashMessages: service(),\n\n        actions: {\n\n          save() {\n            return this.flashMessages.warn('some message');\n          }\n        }\n      });`,\n      options: [FLASH_MESSAGES_CONFIG],\n    },\n\n    // Does not error when dot access decorator is used\n    {\n      filename: 'controllers/dot-access.js',\n      code: `\n      import Controller from '@ember/controller';\n      import SomeMixin from './my-mixin';\n      import EmberObject, { computed } from '@ember/object';\n\n      export default class FoobarTestError extends Controller {\n        @computed.reads('model.actors') actors;\n      }`,\n    },\n  ],\n  invalid: [\n    // Basic store lint error in routes/controllers\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n\n      export default class IndexRoute extends Route {\n        message = 'hello';\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Route from '@ember/routing/route';\n\n      export default class IndexRoute extends Route {\n        @service('store') store;\nmessage = 'hello';\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n    {\n      filename: 'controller/index.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class IndexController extends Controller {\n        @action\n        async loadUsers() {\n          return this.store.findAll('user');\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Controller from '@ember/controller';\n      import { action } from '@ember/object';\n\n      export default class IndexController extends Controller {\n        @service('store') store;\n@action\n        async loadUsers() {\n          return this.store.findAll('user');\n        }\n      }`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n    // Existing import for service injection\n    {\n      filename: 'routes/index.js',\n      code: `\n      import { inject as service } from '@ember/service';\n      import Route from '@ember/routing/route';\n      import Component from '@glimmer/component';\n\n      export default class IndexRoute extends Route {\n        @service('router') router;\n        message = 'hello';\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }\n\n      export class IndexRow extends Component {\n        @service('store') storeService;\n\n        navigate() {\n          this.store.find(this.args.id);\n        }\n      }\n\n\n      export class IndexTable extends Component {\n        loadData() {\n          this.store.find(this.args.id);\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\n      import Route from '@ember/routing/route';\n      import Component from '@glimmer/component';\n\n      export default class IndexRoute extends Route {\n        @service('store') store;\n@service('router') router;\n        message = 'hello';\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }\n\n      export class IndexRow extends Component {\n        @service('store') storeService;\n\n        navigate() {\n          this.store.find(this.args.id);\n        }\n      }\n\n\n      export class IndexTable extends Component {\n        loadData() {\n          this.store.find(this.args.id);\n        }\n      }`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n\n    {\n      filename: 'routes/index.js',\n      code: `\n      import { inject } from '@ember/service';\n      import Route from '@ember/routing/route';\n\n      export default class IndexRoute extends Route {\n        @inject('router') router;\n        message = 'hello';\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n      output: `\n      import { inject } from '@ember/service';\n      import Route from '@ember/routing/route';\n\n      export default class IndexRoute extends Route {\n        @inject('store') store;\n@inject('router') router;\n        message = 'hello';\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n\n    // Custom options\n    {\n      filename: 'components/foobar.js',\n      code: `\n      import Component from '@ember/component';\n\n      export default class FoobarTestError extends Component {\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Component from '@ember/component';\n\n      export default class FoobarTestError extends Component {\n        @service('media') media;\nget isSmallScreen() {\n          return this.media.isXs;\n        }\n      }`,\n      options: [MEDIA_CONFIG],\n      errors: [{ messageId: 'main', data: { serviceName: 'media' }, type: 'MemberExpression' }],\n    },\n    // Custom options with dasherized service name\n    {\n      filename: 'components/foobar.js',\n      code: `\n      import Component from '@ember/component';\n\n      export default class FoobarTestError extends Component {\n        @action\n        save() {\n          return this.flashMessages.warn('some message');\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Component from '@ember/component';\n\n      export default class FoobarTestError extends Component {\n        @service('flash-messages') flashMessages;\n@action\n        save() {\n          return this.flashMessages.warn('some message');\n        }\n      }`,\n      options: [FLASH_MESSAGES_CONFIG],\n      errors: [\n        { messageId: 'main', data: { serviceName: 'flash-messages' }, type: 'MemberExpression' },\n      ],\n    },\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class IndexController extends Controller {\n        async loadData() {\n          return this.store.findAll('rental');\n        }\n      }\n\n      export default class IndexRoute extends Route {\n        async model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class IndexController extends Controller {\n        @service('store') store;\nasync loadData() {\n          return this.store.findAll('rental');\n        }\n      }\n\n      export default class IndexRoute extends Route {\n        @service('store') store;\nasync model() {\n          return this.store.findAll('rental');\n        }\n      }`,\n      errors: [\n        { messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' },\n        { messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' },\n      ],\n    },\n    // Works for modules with multiple module types\n    {\n      filename: 'pods/user.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class User {\n        get mediaAccount() {\n          return this.media.account;\n        }\n      }\n\n      export class UserController extends Controller {\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }\n\n      export class UserRoute extends Route {\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class User {\n        get mediaAccount() {\n          return this.media.account;\n        }\n      }\n\n      export class UserController extends Controller {\n        @service('media') media;\nget isSmallScreen() {\n          return this.media.isXs;\n        }\n      }\n\n      export class UserRoute extends Route {\n        get isSmallScreen() {\n          return this.media.isXs;\n        }\n      }`,\n      options: [MEDIA_CONFIG],\n      errors: [{ messageId: 'main', data: { serviceName: 'media' }, type: 'MemberExpression' }],\n    },\n\n    // Reassesses Module Type for Nested Classes\n    {\n      filename: 'controllers/register.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class RegisterController extends Controller {\n        async loadData() {\n          return this.store.findAll('rental');\n        }\n\n        getMediaUser() {\n          return class Register {\n            get storeInfo() {\n              return this.store.address;\n            }\n          }\n        }\n      }`,\n      output: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export class RegisterController extends Controller {\n        @service('store') store;\nasync loadData() {\n          return this.store.findAll('rental');\n        }\n\n        getMediaUser() {\n          return class Register {\n            get storeInfo() {\n              return this.store.address;\n            }\n          }\n        }\n      }`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n\n    // Nested Ember Module Definition (used in some meta programming instances or decorators)\n    {\n      filename: 'utils/loads-user-controller.js',\n      code: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export function mediaAwareRoute() {\n        return class UserController extends Controller {\n          async loadData() {\n            return this.store.findAll('rental');\n          }\n        }\n      }`,\n      output: `\n      import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n\n      export function mediaAwareRoute() {\n        return class UserController extends Controller {\n          @service('store') store;\nasync loadData() {\n            return this.store.findAll('rental');\n          }\n        }\n      }`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n\n    {\n      code: `\n      import { inject as service } from '@ember/service';\n      import Component from '@ember/component';\n\n      export default class FoobarTestError extends Component {\n        @action\n        save() {\n          return this.flashMessages.warn('some message');\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\n      import Component from '@ember/component';\n\n      export default class FoobarTestError extends Component {\n        @service('flash-messages') flashMessages;\n@action\n        save() {\n          return this.flashMessages.warn('some message');\n        }\n      }`,\n      options: [FLASH_MESSAGES_CONFIG],\n      errors: [\n        { messageId: 'main', data: { serviceName: 'flash-messages' }, type: 'MemberExpression' },\n      ],\n    },\n    // Can detect changed property mapping\n    {\n      filename: 'routes/checkout.js',\n      code: `\n      import { inject as service } from '@ember/service';\n      import Route from '@ember/routing/route';\n\n      export default class CheckoutRoute extends Route {\n        get canVisitCheckout() {\n          return this.featureChecker.isEnabled('checkout');\n        }\n      }`,\n      output: `\n      import { inject as service } from '@ember/service';\n      import Route from '@ember/routing/route';\n\n      export default class CheckoutRoute extends Route {\n        @service('feature') featureChecker;\nget canVisitCheckout() {\n          return this.featureChecker.isEnabled('checkout');\n        }\n      }`,\n      options: [FEATURE_CHECKER_CONFIG],\n      errors: [{ messageId: 'main', data: { serviceName: 'feature' }, type: 'MemberExpression' }],\n    },\n\n    // Can work with services with nested module paths\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class IndexRoute extends Route {\n        model() {\n          return this.checkout.viewCart();\n        }\n      }`,\n      output: `\n      import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n\n      export default class IndexRoute extends Route {\n        @service('cart/checkout') checkout;\nmodel() {\n          return this.checkout.viewCart();\n        }\n      }`,\n      options: [NESTED_SERVICE_CONFIG],\n      errors: [\n        { messageId: 'main', data: { serviceName: 'cart/checkout' }, type: 'MemberExpression' },\n      ],\n    },\n\n    // Check use and fix in legacy ember components\n    {\n      filename: 'pods/index.js',\n      code: `\n        import { inject as service } from '@ember/service';\n        import Component from '@ember/component';\n\n        export default Component.extend({\n          actions: {\n            save() {\n              return this.flashMessages.warn('some message');\n            }\n          }\n        });`,\n      output: `\n        import { inject as service } from '@ember/service';\n        import Component from '@ember/component';\n\n        export default Component.extend({\n          flashMessages: service('flash-messages'),\nactions: {\n            save() {\n              return this.flashMessages.warn('some message');\n            }\n          }\n        });`,\n      options: [FLASH_MESSAGES_CONFIG],\n      errors: [\n        { messageId: 'main', data: { serviceName: 'flash-messages' }, type: 'MemberExpression' },\n      ],\n    },\n\n    // Should not crash when .extend() has a mixin as first arg (#2239)\n    {\n      filename: 'routes/index.js',\n      code: `\n      import Route from '@ember/routing/route';\n\n      export default Route.extend(SomeMixin, {\n        model() {\n          return this.store.findAll('rental');\n        }\n      });`,\n      output: `\n      import { inject as service } from '@ember/service';\nimport Route from '@ember/routing/route';\n\n      export default Route.extend(SomeMixin, {\n        store: service('store'),\nmodel() {\n          return this.store.findAll('rental');\n        }\n      });`,\n      errors: [{ messageId: 'main', data: { serviceName: 'store' }, type: 'MemberExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-implicit-service-injection-argument.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-implicit-service-injection-argument');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst EMBER_IMPORT = \"import Ember from 'ember';\";\nconst INJECT_IMPORT = \"import {inject} from '@ember/service';\";\nconst SERVICE_IMPORT = \"import {inject as service} from '@ember/service';\";\nconst NEW_SERVICE_IMPORT = \"import {service} from '@ember/service';\";\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\nruleTester.run('no-implicit-service-injection-argument', rule, {\n  valid: [\n    // With argument (classic class):\n    `${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service('serviceName') });`,\n    `${INJECT_IMPORT} export default Component.extend({ serviceName: inject('serviceName') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: service('serviceName') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: service('service-name') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: service('random') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: service(\\`service-name\\`) });`,\n\n    // With argument (native class)\n    `${SERVICE_IMPORT} class Test { @service('service-name') serviceName }`,\n    `${NEW_SERVICE_IMPORT} class Test { @service('service-name') serviceName }`,\n\n    // Not Ember's `service()` function (classic class):\n    'export default Component.extend({ serviceName: otherFunction() });',\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: service.foo() });`,\n\n    // Not Ember's `service()` function (native class):\n    `${SERVICE_IMPORT} class Test { @otherDecorator() name }`,\n    `${SERVICE_IMPORT} class Test { @service.foo() name }`,\n    `${SERVICE_IMPORT} class Test { @foo.service() name }`,\n\n    // Spread syntax\n    'export default Component.extend({ ...foo });',\n  ],\n  invalid: [\n    // Classic class\n    {\n      // `service` import\n      code: `${SERVICE_IMPORT} export default Component.extend({ serviceName: service() });`,\n      output: `${SERVICE_IMPORT} export default Component.extend({ serviceName: service('service-name') });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      // `inject` import\n      code: `${INJECT_IMPORT} export default Component.extend({ serviceName: inject() });`,\n      output: `${INJECT_IMPORT} export default Component.extend({ serviceName: inject('service-name') });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      // Property name in string literal.\n      code: `${SERVICE_IMPORT} export default Component.extend({ 'serviceName': service() });`,\n      output: `${SERVICE_IMPORT} export default Component.extend({ 'serviceName': service('service-name') });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    // Decorator:\n    {\n      code: `${SERVICE_IMPORT} class Test { @service() serviceName }`,\n      output: `${SERVICE_IMPORT} class Test { @service('service-name') serviceName }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    // Decorator with new import\n    {\n      code: `${NEW_SERVICE_IMPORT} class Test { @service() serviceName }`,\n      output: `${NEW_SERVICE_IMPORT} class Test { @service('service-name') serviceName }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      // Decorator with no parenthesis\n      code: `${SERVICE_IMPORT} class Test { @service serviceName }`,\n      output: `${SERVICE_IMPORT} class Test { @service('service-name') serviceName }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n    {\n      // No normalization needed.\n      code: `${SERVICE_IMPORT} class Test { @service() foo }`,\n      output: `${SERVICE_IMPORT} class Test { @service('foo') foo }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      // Scoped/nested service name with property name in string literal.\n      code: `${SERVICE_IMPORT} class Test { @service() 'myScope/myService' }`,\n      output: `${SERVICE_IMPORT} class Test { @service('my-scope/my-service') 'myScope/myService' }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-incorrect-calls-with-inline-anonymous-functions.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-incorrect-calls-with-inline-anonymous-functions');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\nruleTester.run('no-incorrect-calls-with-inline-anonymous-functions', rule, {\n  valid: [\n    `\n      import { once, scheduleOnce, debounce } from '@ember/runloop';\n      scheduleOnce('afterRender', this, this.methodToInvokeOnce);\n      scheduleOnce('afterRender', this, methodToInvokeOnce);\n      scheduleOnce('afterRender', methodToInvokeOnce);\n      scheduleOnce('afterRender', this.methodToInvokeOnce);\n      once(this, this.methodToInvokeOnce);\n      once(this, methodToInvokeOnce);\n      once(methodToInvokeOnce);\n      once(this.methodToInvokeOnce);\n      schedule('afterRender', this, this.methodToInvoke);\n      schedule('afterRender', this, methodToInvoke);\n      schedule('afterRender', this.methodToInvoke);\n      schedule('afterRender', methodToInvoke);\n      debounce(this, this.methodToDebounce, 300);\n      debounce(this, methodToDebounce, 300);\n      debounce(this.methodToDebounce, 300);\n      debounce(methodToDebounce, 300);\n\n      // schedule always allows inline functions\n      schedule('afterRender', this, function() {});\n      schedule('afterRender', function() {});\n      schedule('afterRender', () => {});\n\n      someOtherMethod('foo', this, function() {});\n      someOtherMethod('foo', function() {});\n      someOtherMethod(function() {});\n      someOtherMethod(() => {});\n    `,\n    `\n      import { run } from '@ember/runloop';\n      run.scheduleOnce('afterRender', this, this.methodToInvokeOnce);\n      run.scheduleOnce('afterRender', this, methodToInvokeOnce);\n      run.scheduleOnce('afterRender', methodToInvokeOnce);\n      run.scheduleOnce('afterRender', this.methodToInvokeOnce);\n      run.once(this, this.methodToInvokeOnce);\n      run.once(this, methodToInvokeOnce);\n      run.once(methodToInvokeOnce);\n      run.once(this.methodToInvokeOnce);\n      run.debounce(this, this.methodToDebounce, 300);\n      run.debounce(this, methodToDebounce, 300);\n      run.debounce(this.methodToDebounce, 300);\n      run.debounce(methodToDebounce, 300);\n\n      run.schedule('afterRender', this, this.methodToInvoke);\n      run.schedule('afterRender', this, methodToInvoke);\n      run.schedule('afterRender', this.methodToInvoke);\n      run.schedule('afterRender', methodToInvoke);\n\n      // schedule always allows inline functions\n      run.schedule('afterRender', this, function() {});\n      run.schedule('afterRender', function() {});\n      run.schedule('afterRender', () => {});\n\n      someOtherMethod('foo', this, function() {});\n      someOtherMethod('foo', function() {});\n      someOtherMethod(function() {});\n      someOtherMethod(() => {});\n    `,\n    `\n      import { somethingElse } from '@ember/runloop';\n      scheduleOnce('afterRender', this, function() {});\n      scheduleOnce('afterRender', function() {});\n      once(this, function() {});\n      once(function() {});\n      debounce(this, function() {}, 300);\n      debounce(function() {}, 300);\n\n      run.scheduleOnce('afterRender', this, function() {});\n      run.scheduleOnce('afterRender', function() {});\n      run.once(this, function() {});\n      run.once(function() {});\n\n      scheduleOnce('afterRender', this, () => {});\n      scheduleOnce('afterRender', () => {});\n      once(this, () => {});\n      once(() => {});\n      debounce(this, () => {}, 300);\n      debounce(() => {}, 300);\n      run.scheduleOnce('afterRender', this, () => {});\n      run.scheduleOnce('afterRender', () => {});\n      run.once(this, () => {});\n      run.once(() => {});\n    `,\n    `\n      import { run } from 'not-ember';\n\n      run.once(this, function() {});\n      run.scheduleOnce('afterRender', this, function() {});\n      run.once(function() {});\n      run.scheduleOnce('afterRender', function() {});\n      run.debounce(this, function() {}, 300);\n\n      run.once(this, () => {});\n      run.scheduleOnce('afterRender', this, () => {});\n      run.once(() => {});\n      run.scheduleOnce('afterRender', () => {});\n      run.debounce(this, () => {}, 300);\n    `,\n    `\n      import { run } from '@ember/runloop';\n\n      run.once.somethingElse(this, function() {});\n      run.scheduleOnce.somethingElse('afterRender', this, function() {});\n      run.debounce.somethingElse(this, function() {}, 300);\n      somethingElse.run.once(this, function() {});\n      somethingElse.run.scheduleOnce('afterRender', this, function() {});\n      somethingElse.run.debounce(this, function() {}, 300);\n\n      run.once.somethingElse(this, () => {});\n      run.scheduleOnce.somethingElse('afterRender', this, () => {});\n      run.debounce.somethingElse(this, () => {}, 300);\n      somethingElse.run.once(this, () => {});\n      somethingElse.run.scheduleOnce('afterRender', this, () => {});\n      somethingElse.run.debounce(this, () => {}, 300);\n    `,\n    \"someOtherMethod('foo', this, function() {})\",\n    \"someOtherMethod('foo', function() {})\",\n    'someOtherMethod(function() {})',\n    \"import OtherThing from 'other-thing';\",\n    \"import YetOtherThing, { namedThing, anotherNamedThing } from 'yet-other-thing';\",\n  ],\n  invalid: [\n    {\n      code: `\n        import { once } from '@ember/runloop';\n        once(function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { once } from '@ember/runloop';\n        once(this, function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { once, scheduleOnce } from '@ember/runloop';\n        once(function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { scheduleOnce } from '@ember/runloop';\n        scheduleOnce('afterRender', function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { scheduleOnce } from '@ember/runloop';\n        scheduleOnce('afterRender', this, function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { once, scheduleOnce } from '@ember/runloop';\n        scheduleOnce('afterRender', this, function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { debounce } from '@ember/runloop';\n        debounce(this, function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.once(function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.once(this, function() {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.scheduleOnce('afterRender', function() {})\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.scheduleOnce('afterRender', this, function() {})\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { once, scheduleOnce, run } from '@ember/runloop';\n        run.scheduleOnce('afterRender', this, function() {})\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionExpression' }],\n    },\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.scheduleOnce('afterRender', this, () => {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: `\n        import { scheduleOnce } from '@ember/runloop';\n        scheduleOnce('afterRender', this, () => {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: `\n        import { once } from '@ember/runloop';\n        once(this, () => {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n    {\n      code: `\n        import { debounce } from '@ember/runloop';\n        debounce(this, () => {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-incorrect-computed-macros.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-incorrect-computed-macros');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE_AND_OR } = rule;\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\n\nruleTester.run('no-incorrect-computed-macros', rule, {\n  valid: [\n    // Correct usage:\n    `import { and } from '@ember/object/computed';\n     and('someProperty1', 'someProperty2')`,\n    `import { or } from '@ember/object/computed';\n     or('someProperty1', 'someProperty2')`,\n\n    // Spread element:\n    `import { and } from '@ember/object/computed';\n     and(...deps)`,\n    `import { or } from '@ember/object/computed';\n     or(...deps)`,\n\n    // Brace expansion:\n    `import { and } from '@ember/object/computed';\n    and('user.{name,token}')`,\n    `import { or } from '@ember/object/computed';\n    or('user.{name,token}')`,\n\n    // Wrong function:\n    `import { and } from '@ember/object/computed';\n     and.random()`,\n    `import { or } from '@ember/object/computed';\n     or.random()`,\n\n    // Wrong function:\n    `import { and } from '@ember/object/computed';\n     random.and()`,\n    `import { and } from '@ember/object/computed';\n    random.or()`,\n\n    // Not from the right import.\n    'and()',\n    'or()',\n\n    // One test case with decorators:\n    `import { and } from '@ember/object/computed';\n     class Test { @and('someProperty1', 'someProperty2') prop }`,\n  ],\n\n  invalid: [\n    {\n      code: `\n      import { and } from '@ember/object/computed';\n      and()`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_AND_OR,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n      import { or } from '@ember/object/computed';\n      or()`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_AND_OR,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n      import { and } from '@ember/object/computed';\n      and('someProperty')`,\n      output: `\n      import { readOnly } from '@ember/object/computed';\nimport { and } from '@ember/object/computed';\n      readOnly('someProperty')`,\n      errors: [\n        {\n          message: ERROR_MESSAGE_AND_OR,\n          type: 'Identifier',\n        },\n      ],\n    },\n    {\n      code: `\n      import { or } from '@ember/object/computed';\n      or('someProperty')`,\n      output: `\n      import { readOnly } from '@ember/object/computed';\nimport { or } from '@ember/object/computed';\n      readOnly('someProperty')`,\n      errors: [\n        {\n          message: ERROR_MESSAGE_AND_OR,\n          type: 'Identifier',\n        },\n      ],\n    },\n    {\n      // One test case with decorators:\n      code: `\n      import { and } from '@ember/object/computed';\n      class Test { @and('someProperty') prop }`,\n      output: `\n      import { readOnly } from '@ember/object/computed';\nimport { and } from '@ember/object/computed';\n      class Test { @readOnly('someProperty') prop }`,\n      errors: [\n        {\n          message: ERROR_MESSAGE_AND_OR,\n          type: 'Identifier',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-invalid-debug-function-arguments.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-invalid-debug-function-arguments');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { DEBUG_FUNCTIONS, ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst VALID_USAGES_BASIC = [\n  {\n    code: \"myFunction(true, 'My string.');\",\n  },\n  {\n    code: \"Ember.myFunction(true, 'My string.');\",\n  },\n];\n\nconst VALID_USAGES_FOR_EACH_DEBUG_FUNCTION = DEBUG_FUNCTIONS.flatMap((debugFunction) => [\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; OtherClass.${debugFunction}(true, 'My string.');`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}();`,\n  },\n  {\n    code: `Ember.${debugFunction}();`,\n  },\n  {\n    // Missing import:\n    code: `${debugFunction}(true, 'My string.');`,\n  },\n  {\n    // Wrong import:\n    code: `import { ${debugFunction} } from 'something-else'; ${debugFunction}(true, 'My string.');`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.');`,\n  },\n  {\n    code: `Ember.${debugFunction}('My description.');`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', true);`,\n  },\n  {\n    code: `Ember.${debugFunction}('My description.', true);`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', true, { id: 'some-id' });`,\n  },\n  {\n    code: `Ember.${debugFunction}('My description.', true, { id: 'some-id' });`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', CONDITION);`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(DESCRIPTION, true);`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(DESCRIPTION, CONDITION);`,\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}.unrelatedFunction(true, 'My description.');`,\n  },\n  {\n    code: `Ember.${debugFunction}.unrelatedFunction(true, 'My description.');`,\n  },\n]);\n\nconst VALID_USAGES = [...VALID_USAGES_BASIC, ...VALID_USAGES_FOR_EACH_DEBUG_FUNCTION];\n\nconst INVALID_USAGES = DEBUG_FUNCTIONS.flatMap((debugFunction) => [\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, 'My description.');`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n  {\n    code: `Ember.${debugFunction}(true, 'My description.');`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, 'My description.', { id: 'some-id' });`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n  {\n    code: `Ember.${debugFunction}(true, 'My description.', { id: 'some-id' });`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, \\`My \\${123} description.\\`);`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(CONDITION, 'My description.');`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n  {\n    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(CONDITION, \\`My \\${123} description.\\`);`,\n    output: null,\n    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n  },\n]);\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-invalid-debug-function-arguments', rule, {\n  valid: VALID_USAGES,\n  invalid: INVALID_USAGES,\n});\n"
  },
  {
    "path": "tests/lib/rules/no-invalid-dependent-keys.js",
    "content": "const rule = require('../../../lib/rules/no-invalid-dependent-keys');\nconst { addComputedImport } = require('../../helpers/test-case');\nconst RuleTester = require('eslint').RuleTester;\n\nconst {\n  ERROR_MESSAGE_UNBALANCED_BRACES,\n  ERROR_MESSAGE_UNNECESSARY_BRACES,\n  ERROR_MESSAGE_TERMINAL_AT_EACH,\n  ERROR_MESSAGE_MIDDLE_BRACKETS,\n  ERROR_MESSAGE_LEADING_TRAILING_PERIOD,\n  ERROR_MESSAGE_INVALID_CHARACTER,\n} = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\n\neslintTester.run('no-invalid-dependent-keys', rule, {\n  valid: [\n    // Normal usage\n    '{ test: computed(\"a\", \"b\", function() {}) }',\n    '{ test: computed(function() {}) }',\n    '{ test: computed() }',\n    '{ test: computed(SOME_VAR, function() {}) }',\n    '{ test: computed(...SOME_ARRAY, function() {}) }',\n    '{ test: computed(\"a.test\", \"b.test\", function() {}) }',\n    '{ test: computed(\"a.[]\", function() {}) }',\n    '{ test: computed(\"a.@each.id\", function() {}) }',\n\n    // Braces\n    '{ test: computed(\"a.{test,test2}\", \"b\", function() {}) }',\n    '{ test: computed(\"a.{test,test2}\", \"c\", \"b\", function() {}) }',\n    '{ test: computed(\"model.a.{test,test2}\", \"model.b.{test3,test4}\", function() {}) }',\n    '{ test: computed(\"foo.bar.{name,place}\", \"foo.qux.[]\", \"foo.baz.{thing,@each.stuff}\", function() {}) }',\n    '{ test: computed(\"foo.bar.{name,place,baz,stuff,test.@each.content}\",\"foo.bar.fields.@each.{name,value}\",\"foo.bar.custom.@each.{question,choice}\", function() {})}',\n    '{ test: computed(\"foo1.bar1.{name1,place1,baz1,stuff1,test1.@each.content1,fields1.@each.{name1,value1},custom1.@each.{question1,choice1}}\", function() {})}',\n\n    // Macros\n    '{ test: computed.or(\"model.{projects,files.@each.{isSaving,test}}\", \"foo.bar.place\") }',\n    '{ test: computed.or(\"foo.bar.name\", \"foo.bar.place\") }',\n    '{ test: computed.or(\"foo.bar.{name,place}\") }',\n    '{ test: computed.and(\"foo.bar.name\", \"foo.bar.place\") }',\n    '{ test: Ember.computed.filterBy(\"a\", \"b\", false) }',\n\n    // Unrelated functions\n    'myFunction(\"{ key: string }, saving,test}\", \"b}\", false)',\n    'myFunction(\"A string containing curly braces {}}\")',\n  ].map(addComputedImport),\n  invalid: [\n    // Unbalanced braces\n    {\n      code: '{ test: computed(\"foo.{name,place\", \"foo.name,place}\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNBALANCED_BRACES,\n          type: 'Literal',\n        },\n        {\n          message: ERROR_MESSAGE_UNBALANCED_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"foo.{bar.{name,place},qux.[],{thing,@each.stuff}\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNBALANCED_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n    {\n      code: \"import Ember from 'ember'; { test: Ember.computed('foo.{bar.{name,place},qux.[],{thing,@each.stuff}', function() {}) }\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNBALANCED_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n    {\n      code: '{ test:  computed(\"foo.{bar.{name,place,baz,stuff,test.@each.content}\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNBALANCED_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n    {\n      code: '{ test:  computed(\"foo1.bar1.{name1,place1,baz1,stuff1,test1.@each.content1,fields1.@each.{name1,value1},custom1.@each.{question1,choice1}}}\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNBALANCED_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n\n    // Unnecessary braces\n    {\n      code: '{ test: computed(\"foo.{bar}\", function() {}) }',\n      output: '{ test: computed(\"foo.bar\", function() {}) }',\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNNECESSARY_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"foo.{bar}.{abc}\", function() {}) }',\n      output: '{ test: computed(\"foo.bar.abc\", function() {}) }',\n      errors: [\n        {\n          message: ERROR_MESSAGE_UNNECESSARY_BRACES,\n          type: 'Literal',\n        },\n      ],\n    },\n\n    // @each\n    {\n      code: 'computed(\"foo.@each\", function() {})',\n      output: 'computed(\"foo.[]\", function() {})',\n      errors: [\n        {\n          message: ERROR_MESSAGE_TERMINAL_AT_EACH,\n          type: 'Literal',\n        },\n      ],\n    },\n\n    // []\n    {\n      code: 'computed(\"foo.[].bar\", function() {})',\n      output: 'computed(\"foo.@each.bar\", function() {})',\n      errors: [\n        {\n          message: ERROR_MESSAGE_MIDDLE_BRACKETS,\n          type: 'Literal',\n        },\n      ],\n    },\n\n    // Extra periods\n    {\n      code: 'computed(\".foo.bar\", function() {})',\n      output: 'computed(\"foo.bar\", function() {})',\n      errors: [\n        {\n          message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,\n          type: 'Literal',\n        },\n      ],\n    },\n    {\n      code: 'computed(\"foo.bar.\", function() {})',\n      output: 'computed(\"foo.bar\", function() {})',\n      errors: [\n        {\n          message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,\n          type: 'Literal',\n        },\n      ],\n    },\n\n    // Spaces\n    {\n      code: 'computed(\" foo . bar\", function() {})',\n      output: 'computed(\"foo.bar\", function() {})',\n      errors: [\n        {\n          message: ERROR_MESSAGE_INVALID_CHARACTER,\n          type: 'Literal',\n        },\n      ],\n    },\n\n    // Decorator\n    {\n      code: \"class Test { @computed('foo.{name,place') get someProp() { return true; } }\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_UNBALANCED_BRACES, type: 'Literal' }],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/no-invalid-test-waiters.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-invalid-test-waiters');\n\nconst { MODULE_SCOPE_ERROR_MESSAGE, DIRECT_ASSIGNMENT_ERROR_MESSAGE } = rule;\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-invalid-test-waiters', rule, {\n  valid: [\n    `\n    import { buildWaiter } from 'ember-test-waiters';\n    import { random } from 'random';\n    import { somethingElse } from 'ember-test-waiters';\n\n    let myWaiter = buildWaiter('waiterName');\n  `,\n    `\n    import { buildWaiter } from 'table-waiters';\n\n    function useWaiter() {\n      let myOtherWaiter = buildWaiter('the second');\n    }\n  `,\n    `\n    import { buildWaiter } from 'ember-test-waiters';\n\n    function useWaiter() {\n      let myWaiter = somethingElse.buildWaiter('waiterName');\n    }\n  `,\n    `\n    import { buildWaiter } from 'ember-test-waiters';\n\n    function useWaiter() {\n      let myWaiter = buildWaiter.somethingElse('waiterName');\n    }\n  `,\n    `\n    import { buildWaiter } from 'ember-test-waiters';\n\n    buildWaiter.somethingElse('waiterName');\n    somethingElse.buildWaiter('waiterName');\n  `,\n  ],\n\n  invalid: [\n    {\n      code: `\n      import { buildWaiter } from 'ember-test-waiters';\n      import { random } from 'random';\n      import { somethingElse } from 'ember-test-waiters';\n\n      function useWaiter() {\n          let myOtherWaiter = buildWaiter('the second');\n      }\n      `,\n      output: null,\n      errors: [{ message: MODULE_SCOPE_ERROR_MESSAGE }],\n    },\n    {\n      code: `\n      import { buildWaiter as b } from 'ember-test-waiters';\n\n      function useWaiter() {\n          let myOtherWaiter = b('the second');\n      }\n      `,\n      output: null,\n      errors: [{ message: MODULE_SCOPE_ERROR_MESSAGE }],\n    },\n    {\n      code: `\n      import { buildWaiter } from 'ember-test-waiters';\n\n      const someFunction = () => {\n        buildWaiter('waiterName');\n      };\n      `,\n      output: null,\n      errors: [{ message: DIRECT_ASSIGNMENT_ERROR_MESSAGE }],\n    },\n    {\n      code: `\n      import { buildWaiter } from 'ember-test-waiters';\n\n      buildWaiter('waiterName');\n      `,\n      output: null,\n      errors: [{ message: DIRECT_ASSIGNMENT_ERROR_MESSAGE }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-jquery.js",
    "content": "const rule = require('../../../lib/rules/no-jquery');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('no-jquery', rule, {\n  valid: [\n    `\n        export default Ember.Component({\n          didInsertElement() {\n            this.element.classList.add('active')\n          }\n        });`,\n    {\n      filename: 'example-app/tests/integration/component/some-component-test.js',\n      code: `\n        import { moduleForComponent, test } from 'ember-qunit';\n        import hbs from 'htmlbars-inline-precompile';\n\n        moduleForComponent('some-component', 'Integration | Component | some-component', {\n          integration: true\n        });\n\n        test('assert something', function() {\n          assert.equal(find('.some-component').textContent.trim(), 'hello world');\n        })`,\n    },\n    `\n      function addClass($, selector, className) {\n        $(selector).addClass(className);\n      }\n    `,\n    `\n      Foo.$(selector).attr(attribute);\n    `,\n    `\n      const { $ } = Foo;\n      $(selector).attr(attribute);\n    `,\n  ],\n  invalid: [\n    // Global $\n    {\n      code: `\n        export default Ember.Component({\n          didInsertElement() {\n            $(body).addClass('active')\n          }\n        });`,\n      output: null,\n      globals: {\n        $: true,\n      },\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Service from '@ember/service';\n        export default Service.extend({\n          myFunc(a, b) {\n            return $.extend({}, a, b);\n          }\n        });`,\n      output: null,\n      globals: {\n        $: true,\n      },\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // import $\n    {\n      code: `\n        import $ from 'jquery';\n        export default Ember.Component({\n          didInsertElement() {\n            $(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import $ from 'jquery';\n        import Service from '@ember/service';\n        export default Service.extend({\n          myFunc(a, b) {\n            return $.extend({}, a, b);\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // aliased import $ from jquery\n    {\n      code: `\n        import jq from 'jquery';\n        import Service from '@ember/service';\n        export default Service.extend({\n          myFunc(a, b) {\n            jq.post({}, a, b);\n            return jq.extend({}, a, b);\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n          line: 6,\n        },\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n          line: 7,\n        },\n      ],\n    },\n    // Ember.$\n    {\n      code: `\n        import Ember from 'ember'\n        export default Ember.Component({\n          didInsertElement() {\n            Ember.$(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // AliasedEmber.$\n    {\n      code: `\n        import E from 'ember';\n        export default E.Component({\n          didInsertElement() {\n            E.$(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // import $ from ember\n    {\n      code: `\n        import Ember, { $ } from 'ember';\n        export default Ember.Component({\n          didInsertElement() {\n            $(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // const jq = Ember.$\n    {\n      code: `\n        import Ember from 'ember'\n        const jq = Ember.$;\n        export default Ember.Component({\n          didInsertElement() {\n            jq(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // const { $ } = Ember;\n    {\n      code: `\n        import Ember from 'ember'\n        const { $ } = Ember;\n        export default Ember.Component({\n          didInsertElement() {\n            $(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          line: 6,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // const { $: jq } = Ember;\n    {\n      code: `\n        import Ember from 'ember'\n        const { $: jq } = Ember;\n        export default Ember.Component({\n          didInsertElement() {\n            jq(body).addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          line: 6,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // this.$\n    {\n      code: `\n        export default Ember.Component({\n          didInsertElement() {\n            this.$().addClass('active')\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        export default Ember.Component({\n          didInsertElement() {\n            this.$.extend({}, a, b)\n          }\n        });`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'MemberExpression',\n        },\n      ],\n    },\n    {\n      filename: 'example-app/tests/integration/component/some-component-test.js',\n      code: `\n        import { moduleForComponent, test } from 'ember-qunit';\n        import hbs from 'htmlbars-inline-precompile';\n\n        moduleForComponent('some-component', 'Integration | Component | some-component', {\n          integration: true\n        });\n\n        test('assert something', function() {\n          assert.equal(this.$('.some-component').text().trim(), 'hello world');\n        })`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-legacy-test-waiters.js",
    "content": "const rule = require('../../../lib/rules/no-legacy-test-waiters');\n\nconst { ERROR_MESSAGE } = rule;\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-legacy-test-waiters', rule, {\n  valid: [\n    `\n      import Component from '@ember/component';\n      import { buildWaiter } from 'ember-test-waiters';\n\n      let waiter = buildWaiter('my-waiter');\n\n      export default Component.extend({\n        init() {\n          let token = waiter.beginAsync();\n\n          someAsync()\n            .finally(() => waiter.endAsync(token));\n        }\n      });`,\n    `\n      import { registerWaiter } from 'table-waiters';\n\n      registerWaiter();\n    `,\n    `\n      import { unregisterWaiter } from 'table-waiters';\n\n      unregisterWaiter();\n    `,\n    `\n      import { registerHelper } from '@ember/test';\n      registerHelper();\n\n      // Other calls:\n      registerWaiter();\n      unregisterWaiter();\n      otherCall();\n      SomeObject.otherCall();\n    `,\n    `\n      import { registerWaiter, unregisterWaiter } from '@ember/test';\n\n      // Wrong function calls but look similar.\n\n      SomeObject.registerWaiter();\n      registerWaiter.otherFunction();\n\n      SomeObject.unregisterWaiter();\n      unregisterWaiter.otherFunction();\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n        import { registerWaiter } from '@ember/test';\n\n        registerWaiter(() => {\n          return counter === 0;\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import { registerWaiter as reg } from '@ember/test';\n\n        reg(() => {\n          return counter === 0;\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import { registerWaiter, unregisterWaiter } from '@ember/test';\n\n        registerWaiter(waiter);\n        unregisterWaiter(waiter);\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],\n    },\n    {\n      code: `\n        import { registerWaiter as reg, unregisterWaiter as unreg } from '@ember/test';\n\n        reg(waiter);\n        unreg(waiter);\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-mixins.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-mixins');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE } = rule;\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\neslintTester.run('no-mixins', rule, {\n  valid: [\n    `\n    import NoMixinRule from \"some/addon/mixin\";\n    export default mixin;\n    `,\n    `\n    import NameIsMixin from \"some/addon\";\n    export default Component.extend({});\n  `,\n    `\n    import * as Mixins from \"some/addon/mixins\";\n    export default Component.extend({});\n  `,\n    `\n    import Mixin from '@ember/object/mixin';\n    const newMixin = Mixin.create({});\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n        import BadCode from \"my-addon/mixins/bad-code\";\n\n        export default Component.extend(BadCode, {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n      import EmberObject from '@ember/object';\n      import EditableMixin from '../mixins/editable';\n\n      const Comment = EmberObject.extend(EditableMixin, {\n        post: null\n      });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: `\n      import Component from '@ember/component';\n      import FooMixin from '../utils/mixins/foo';\n\n      export default class FooComponent extends Component.extend(FooMixin) {\n        // ...\n      }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-modifier-argument-destructuring.js",
    "content": "const rule = require('../../../lib/rules/no-modifier-argument-destructuring');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-modifier-argument-destructuring', rule, {\n  valid: [\n    // Not importing from ember-modifier\n    `\n      function modifier(fn) { return fn; }\n      modifier((element, [text]) => {});\n    `,\n\n    // No destructuring of positional args\n    `\n      import { modifier } from 'ember-modifier';\n      modifier((element, positional) => {\n        element.addEventListener('hover', () => console.log(positional[0]));\n      });\n    `,\n\n    // No destructuring of named args\n    `\n      import { modifier } from 'ember-modifier';\n      modifier((element, positional, named) => {\n        element.addEventListener('hover', () => console.log(named.text));\n      });\n    `,\n\n    // Only element parameter\n    `\n      import { modifier } from 'ember-modifier';\n      modifier((element) => {\n        element.addEventListener('click', () => {});\n      });\n    `,\n\n    // No arguments at all\n    `\n      import { modifier } from 'ember-modifier';\n      modifier(() => {});\n    `,\n\n    // Using a non-modifier function from ember-modifier\n    `\n      import { something } from 'ember-modifier';\n      something((element, [text]) => {});\n    `,\n\n    // Modifier called with non-function argument\n    `\n      import { modifier } from 'ember-modifier';\n      modifier(myCallback);\n    `,\n\n    // Destructuring element (first param) is fine as it's not tracked\n    `\n      import { modifier } from 'ember-modifier';\n      modifier(({ tagName }) => {});\n    `,\n\n    // Function expression (not arrow) without destructuring\n    `\n      import { modifier } from 'ember-modifier';\n      modifier(function(element, positional) {\n        console.log(positional[0]);\n      });\n    `,\n  ],\n\n  invalid: [\n    // Destructuring positional args (arrow function)\n    {\n      code: `\n        import { modifier } from 'ember-modifier';\n        modifier((element, [text]) => {\n          element.addEventListener('hover', () => console.log(text));\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrayPattern' }],\n    },\n\n    // Destructuring positional args (function expression)\n    {\n      code: `\n        import { modifier } from 'ember-modifier';\n        modifier(function(element, [text]) {\n          element.addEventListener('hover', () => console.log(text));\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrayPattern' }],\n    },\n\n    // Destructuring named args\n    {\n      code: `\n        import { modifier } from 'ember-modifier';\n        modifier((element, positional, { text }) => {\n          element.addEventListener('hover', () => console.log(text));\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ObjectPattern' }],\n    },\n\n    // Both positional and named args destructured\n    {\n      code: `\n        import { modifier } from 'ember-modifier';\n        modifier((element, [text], { title }) => {\n          element.addEventListener('hover', () => console.log(text, title));\n        });\n      `,\n      output: null,\n      errors: [\n        { message: ERROR_MESSAGE, type: 'ArrayPattern' },\n        { message: ERROR_MESSAGE, type: 'ObjectPattern' },\n      ],\n    },\n\n    // Renamed import\n    {\n      code: `\n        import { modifier as createModifier } from 'ember-modifier';\n        createModifier((element, [text]) => {\n          element.addEventListener('hover', () => console.log(text));\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrayPattern' }],\n    },\n\n    // Multiple positional args destructured\n    {\n      code: `\n        import { modifier } from 'ember-modifier';\n        modifier((element, [text, count]) => {\n          element.addEventListener('hover', () => console.log(text, count));\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ArrayPattern' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-new-mixins.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-new-mixins');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst { ERROR_MESSAGE } = rule;\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\neslintTester.run('no-new-mixins', rule, {\n  valid: [\n    `\n        import mixin from \"some/addon\";\n        export default mixin;\n      `,\n    'export default mixin.create({actions: {},});',\n  ],\n  invalid: [\n    {\n      code: `\n        import Ember from \"ember\";\n\n        export default Ember.Mixin.create({});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Mixin from \"@ember/object/mixin\";\n\n        export default Mixin.create({});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Mixin from \"@ember/object/mixin\";\n        class MyMixin extends Mixin {}\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ClassDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-noop-setup-on-error-in-before.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-noop-setup-on-error-in-before');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-noop-setup-on-error-in-before', rule, {\n  valid: [\n    `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function(hooks) {\n          test('something', function() {\n            setupOnerror((error) => {\n              assert.equal(error.message, 'test', 'Should have message');\n            });\n          })\n        });\n        `,\n    `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.beforeEach(function() {\n            setupOnerror((error) => {\n              assert.equal(error.message, 'test', 'Should have message');\n            });\n          });\n        });\n      `,\n    `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function(hooks) {\n          test('something', function() {\n            setupOnerror(() => {\n            });\n          })\n        });\n      `,\n    `\n      import { setupOnerror } from '@ember/test-helpers';\n      import { module, test } from 'qunit';\n      import moduleBody from 'somewhere';\n\n      module('foo', moduleBody());\n    `,\n    `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function() {\n        });\n      `,\n  ],\n  invalid: [\n    {\n      code: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module as moduleVariable } from 'qunit';\n\n        moduleVariable('foo', function(hooks) {\n          hooks.beforeEach(function() {\nsetupOnerror(() => {});\n          });\n        });\n      `,\n      output: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module as moduleVariable } from 'qunit';\n\n        moduleVariable('foo', function(hooks) {\n          hooks.beforeEach(function() {\n\n          });\n        });\n      `,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.beforeEach(function() {\nsetupOnerror(function(){});\n          });\n        });\n      `,\n      output: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.beforeEach(function() {\n\n          });\n        });\n      `,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.beforeEach(function() {\nsetupOnerror(function noop(){});\n          });\n        });\n      `,\n      output: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.beforeEach(function() {\n\n          });\n        });\n      `,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.before(function() {\nsetupOnerror(() => {});\n          });\n        });\n      `,\n      output: `\n        import { setupOnerror } from '@ember/test-helpers';\n        import { module } from 'qunit';\n\n        module('foo', function(hooks) {\n          hooks.before(function() {\n\n          });\n        });\n      `,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-observers.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-observers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: {\n      legacyDecorators: true,\n    },\n  },\n});\n\neslintTester.run('no-observers', rule, {\n  valid: [\n    'export default Controller.extend();',\n    'export default Controller.extend({actions: {},});',\n\n    `\n    import { action } from '@ember/object';\n    class FooComponent extends Component {\n      @action\n      clickHandler() {}\n    }`,\n\n    // Not an Ember observer:\n    'import { observer } from \"@ember/object\"; observer.foo();',\n    'import { observer } from \"@ember/object\"; foo.observer();',\n    'import { observer } from \"@ember/object\"; foo.observer.bar()',\n    'import { observer } from \"@ember/object\"; foo()',\n    'import Ember from \"ember\"; import { observer } from \"@ember/object\"; Ember.foo()',\n\n    // Import missing:\n    'observer();',\n    'Ember.observer();',\n    'observes();',\n    'addObserver();',\n\n    // removeObserver is allowed (we only need to report addObserver).\n    'import { removeObserver } from \"@ember/object/observers\"; removeObserver();',\n    'import Ember from \"ember\"; Ember.removeObserver();',\n  ],\n  invalid: [\n    {\n      code: `\n        export default Component.extend({\n          fooBar() {\n            this.foo.addObserver(\"bar\", this, \"rerun\");\n          }\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        export default Component.extend({\n          fooBar() {\n            this.addObserver(\"bar\", this, \"rerun\");\n          }\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        export default Component.extend({\n          fooBar(baz) {\n            baz.addObserver(\"bar\", this, \"rerun\");\n          }\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: 'import Ember from \"ember\"; Ember.addObserver(\"foo\", this, \"rerun\");',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: 'import { addObserver } from \"@ember/object/observers\"; addObserver();',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; Ember.observer(\"text\", function() {});',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: 'import { observer } from \"@ember/object\"; observer(\"text\", function() {});',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n      import { observes } from '@ember-decorators/object';\n      class FooComponent extends Component {\n        @observes('baz')\n        bar() {}\n      }`,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Decorator',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-old-shims.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-old-shims');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('no-old-shims', rule, {\n  valid: [\"import Ember from 'ember';\", \"import RSVP from 'rsvp';\"],\n  invalid: [\n    {\n      code: \"import Component from 'ember-component';\",\n      output: \"import Component from '@ember/component';\",\n      errors: [{ message: \"Don't use import paths from ember-cli-shims\" }],\n    },\n    {\n      code: \"import { capitalize, dasherize, foo } from 'ember-string';\",\n      output:\n        \"import { capitalize, dasherize } from '@ember/string';\\nimport { foo } from 'ember-string';\",\n      errors: [{ message: \"Don't use import paths from ember-cli-shims\" }],\n    },\n    {\n      code: \"import computed, { not } from 'ember-computed';\",\n      output:\n        \"import { computed } from '@ember/object';\\nimport { not } from '@ember/object/computed';\",\n      errors: [{ message: \"Don't use import paths from ember-cli-shims\" }],\n    },\n    {\n      code: \"import { log } from 'ember-debug';\",\n      output: \"import { debug as log } from '@ember/debug';\",\n      errors: [{ message: \"Don't use import paths from ember-cli-shims\" }],\n    },\n    {\n      code: \"import { log as debug } from 'ember-debug';\",\n      output: \"import { debug } from '@ember/debug';\",\n      errors: [{ message: \"Don't use import paths from ember-cli-shims\" }],\n    },\n    {\n      code: \"import Sortable from 'ember-controllers/sortable';\",\n      output: \"import Sortable from 'ember-controllers/sortable';\", // eslint-disable-line eslint-plugin/prefer-output-null\n      errors: [{ message: \"Don't use import paths from ember-cli-shims\" }],\n    },\n    {\n      code: \"import Service from 'ember-service';\\nimport inject from 'ember-service/inject';\",\n      output: \"import Service from '@ember/service';\\nimport { inject } from '@ember/service';\",\n      errors: [\n        { message: \"Don't use import paths from ember-cli-shims\" },\n        { message: \"Don't use import paths from ember-cli-shims\" },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-on-calls-in-components.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-on-calls-in-components');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nconst message = \"Don't use .on() for component lifecycle events.\";\n\neslintTester.run('no-on-calls-in-components', rule, {\n  valid: [\n    'export default Component.extend();',\n    'export default Component.extend({actions: {}});',\n    'export default Component.extend({abc: service()});',\n    'export default Component.extend({abc: inject.service()});',\n    'export default Component.extend({abc: false});',\n    'export default Component.extend({classNames: [\"abc\", \"def\"]});',\n    'export default Component.extend({abc: computed(function () {})});',\n    'export default Component.extend({abc: observer(\"abc\", function () {})});',\n    'export default Component.extend({abc: observer(\"abc\", function () {test.on(\"xyz\", def)})});',\n    'export default Component.extend({abc: function () {test.on(\"xyz\", def)}});',\n    'export default Component.extend({abc() {$(\"body\").on(\"click\", def)}});',\n    'export default Component.extend({didInsertElement() {$(\"body\").on(\"click\", def).on(\"click\", function () {})}});',\n    'export default Component.extend({actions: {abc() {test.on(\"xyz\", def)}}});',\n    'export default Component.extend({actions: {abc() {$(\"body\").on(\"click\", def).on(\"click\", function () {})}}});',\n    'export default Component.extend({abc: on(\"nonLifecycleEvent\", function() {})});',\n    {\n      code: `\n      let foo = { bar: 'baz' };\n\n      export default Component.extend({\n        ...foo,\n      });\n      `,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n  ],\n  invalid: [\n    {\n      code: `export default Component.extend({\n        test: on(\"didInsertElement\", function () {})\n      });`,\n      output: null,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      // With object variable.\n      code: 'const body = { test: on(\"didInsertElement\", function () {}) }; export default Component.extend(body);',\n      output: null,\n      errors: [{ message, line: 1 }],\n    },\n    {\n      code: `export default Component.extend({\n        test: on(\"init\", observer(\"someProperty\", function () {\n          return true;\n        })),\n        someComputedProperty: computed.bool(true)\n      });`,\n      output: null,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component.extend({\n        test: Ember.on(\"didInsertElement\", function () {}),\n        someComputedProperty: Ember.computed.readOnly('Hello World!'),\n        anotherTest: Ember.on(\"willDestroyElement\", function () {})\n      });`,\n      output: null,\n      errors: [\n        { message, line: 2 },\n        { message, line: 4 },\n      ],\n    },\n    {\n      filename: 'example-app/components/some-component/component.js',\n      code: 'export default CustomComponent.extend({test: on(\"didInsertElement\", function () {})});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use .on() for component lifecycle events.\",\n        },\n      ],\n    },\n    {\n      filename: 'example-app/components/some-component.js',\n      code: 'export default CustomComponent.extend({test: on(\"didInsertElement\", function () {})});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use .on() for component lifecycle events.\",\n        },\n      ],\n    },\n    {\n      filename: 'example-app/twisted-path/some-file.js',\n      code: 'export default Component.extend({test: on(\"didInsertElement\", function () {})});',\n      output: null,\n      errors: [\n        {\n          message: \"Don't use .on() for component lifecycle events.\",\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-pause-test.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-pause-test');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst TEST_FILE_NAME = 'some-test.js';\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-pause-test', rule, {\n  valid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: \"run(() => { console.log('Hello World.'); });\",\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'myCustomClass.pauseTest(myFunction);',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: \"import { pauseTest } from '@ember/test-helpers'; pauseTest.otherFunction(myFunction);\",\n    },\n    {\n      // Missing import:\n      filename: TEST_FILE_NAME,\n      code: 'pauseTest();',\n    },\n    {\n      filename: 'not-a-test-file.js',\n      code: 'async () => { await this.pauseTest(); }',\n    },\n  ],\n  invalid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: 'async () => { await this.pauseTest(); }',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: \"import { pauseTest } from '@ember/test-helpers'; pauseTest();\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      // Renamed import:\n      filename: TEST_FILE_NAME,\n      code: \"import { pauseTest as pt } from '@ember/test-helpers'; pt();\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `\n        test('foo', async function(assert) {\n          await this.pauseTest();\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `\n        import { pauseTest } from '@ember/test-helpers';\n        test('bar', async function(assert) {\n          await pauseTest();\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `\n        test('baz', function(assert) {\n          this.pauseTest();\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-private-routing-service.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-private-routing-service');\nconst RuleTester = require('eslint').RuleTester;\n\nconst {\n  PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE,\n  ROUTER_MICROLIB_ERROR_MESSAGE,\n  ROUTER_MAIN_ERROR_MESSAGE,\n} = rule;\n\nconst EMBER_IMPORT = \"import Ember from 'ember';\";\nconst SERVICE_IMPORT = \"import {inject as service} from '@ember/service';\";\nconst NEW_SERVICE_IMPORT = \"import {service} from '@ember/service';\";\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-private-routing-service', rule, {\n  valid: [\n    // Classic\n    `${SERVICE_IMPORT} export default Component.extend({ someService: service('routing') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ someService: service('-router') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ '-routing': service('routing') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ '-routing': service('-router') });`,\n    `${EMBER_IMPORT} export default Component.extend({ someService: Ember.inject.service('routing') });`,\n    `${EMBER_IMPORT} export default Component.extend({ someService: Ember.inject.service('-router') });`,\n    `${EMBER_IMPORT} export default Component.extend({ '-routing': Ember.inject.service('routing') });`,\n    `${EMBER_IMPORT} export default Component.extend({ '-routing': Ember.inject.service('-router') });`,\n    \"Component.extend({ routing: someOtherFunction('-routing') });\",\n    `${SERVICE_IMPORT} export default Component.extend({ someService: service() });`,\n    'export default Component.extend({ notAService: \"a value\" });',\n    'export default Component.extend({ anInt: 25 });',\n\n    // Octane\n    `${SERVICE_IMPORT} export default class MyComponent extends Component { @service router; }`,\n    `${SERVICE_IMPORT} export default class MyComponent extends Component { @service('router') routing; }`,\n    `${SERVICE_IMPORT} export default class MyComponent extends Component { @service routing; }`,\n    `${SERVICE_IMPORT} export default class MyComponent extends Component { @service('routing') routing; }`,\n    `${NEW_SERVICE_IMPORT} export default class MyComponent extends Component { @service('routing') routing; }`,\n    `\n    export default class MyComponent extends Component {\n      @computed('-routing', 'lastName')\n        get fullName() {\n        return \\`${this.firstName} ${this.lastName}\\`;\n      }\n    }\n    `,\n    `${SERVICE_IMPORT} class MyComponent extends Component { @service() routing; }`,\n    `${SERVICE_IMPORT} class MyComponent extends Component { @service() notRouting; }`,\n    'class MyComponent extends Component { aProp=\"routing\"; }',\n    'class MyComponent extends Component { aProp=\"-routing\"; }',\n    'class MyComponent extends Component { aProp=\"another value\"; }',\n    'class MyComponent extends Component { anIntProp=25; }',\n\n    // _routerMicrolib\n    { code: \"get(this, 'router._routerMicrolib');\", options: [{ catchRouterMicrolib: false }] },\n    { code: 'this.router._routerMicrolib;', options: [{ catchRouterMicrolib: false }] },\n    \"get(this, 'router.somethingElse');\",\n    'this.router.somethingElse;',\n\n    // router:main\n    { code: \"getOwner(this).lookup('router:main');\", options: [{ catchRouterMain: false }] },\n    { code: \"owner.lookup('router:main');\", options: [{ catchRouterMain: false }] },\n    { code: \"this.owner.lookup('router:main');\", options: [{ catchRouterMain: false }] },\n    \"getOwner(this).lookup('router:somethingElse');\",\n    \"owner.lookup('router:somethingElse');\",\n    \"this.owner.lookup('router:somethingElse');\",\n  ],\n  invalid: [\n    // Classic\n    {\n      code: `${SERVICE_IMPORT} export default Component.extend({ routing: service('-routing') });`,\n      output: null,\n      errors: [{ message: PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE, type: 'Property' }],\n    },\n\n    // Octane\n    {\n      code: `${SERVICE_IMPORT} export default class MyComponent extends Component { @service('-routing') routing; }`,\n      output: null,\n      errors: [\n        {\n          message: PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    // Octane, new import\n    {\n      code: `${NEW_SERVICE_IMPORT} export default class MyComponent extends Component { @service('-routing') routing; }`,\n      output: null,\n      errors: [\n        {\n          message: PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    // Octane, new import, renamed\n    {\n      code: \"import {service as aliasedService} from '@ember/service'; export default class MyComponent extends Component { @aliasedService('-routing') routing; }\",\n      output: null,\n      errors: [\n        {\n          message: PRIVATE_ROUTING_SERVICE_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n\n    // _routerMicrolib (`catchRouterMicrolib` option on)\n    {\n      code: \"get(this, 'router._routerMicrolib');\",\n      output: null,\n      errors: [{ message: ROUTER_MICROLIB_ERROR_MESSAGE, type: 'Literal' }],\n    },\n    {\n      code: \"get(this, 'router._router._routerMicrolib');\",\n      output: null,\n      errors: [{ message: ROUTER_MICROLIB_ERROR_MESSAGE, type: 'Literal' }],\n    },\n    {\n      code: 'this.router._routerMicrolib;',\n      output: null,\n      errors: [{ message: ROUTER_MICROLIB_ERROR_MESSAGE, type: 'Identifier' }],\n    },\n    {\n      code: 'this.router._router._routerMicrolib;',\n      output: null,\n      errors: [{ message: ROUTER_MICROLIB_ERROR_MESSAGE, type: 'Identifier' }],\n    },\n\n    // router:main (`catchRouterMain` option on)\n    {\n      code: \"getOwner(this).lookup('router:main');\",\n      output: null,\n      errors: [{ message: ROUTER_MAIN_ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"owner.lookup('router:main');\",\n      output: null,\n      errors: [{ message: ROUTER_MAIN_ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: \"this.owner.lookup('router:main');\",\n      output: null,\n      errors: [{ message: ROUTER_MAIN_ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      // Optional chaining.\n      code: \"this?.owner?.lookup?.('router:main');\",\n      output: null,\n      errors: [{ message: ROUTER_MAIN_ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-proxies.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-proxies');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-proxies', rule, {\n  valid: [\n    \"import ANYTHING from '@ember/object';\",\n    \"import { ANYTHING } from '@ember/object';\",\n\n    \"import ObjectProxy from 'something/else';\",\n    \"import { ObjectProxy } from 'something/else';\",\n  ],\n  invalid: [\n    {\n      code: \"import ObjectProxy from '@ember/object/proxy';\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n    {\n      code: \"import ArrayProxy from '@ember/array/proxy';\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-replace-test-comments.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst RuleTester = require('eslint').RuleTester;\nconst rule = require('../../../lib/rules/no-replace-test-comments');\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester();\nruleTester.run('no-replace-test-comments', rule, {\n  valid: [\n    '// some harmless comment',\n    'myCodeWithoutAComment = \"fooBar\"',\n    {\n      filename: 'app/some-app-file.js',\n      code: '// Replace this with your real tests',\n    },\n  ],\n\n  invalid: [\n    {\n      filename: 'test/some-app-file-test.js',\n      code: '// Replace this with your real tests',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Line',\n        },\n      ],\n    },\n    {\n      filename: 'test/some-app-file-test.js',\n      code: '// TODO: Replace this with your real tests',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Line',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-restricted-property-modifications.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-restricted-property-modifications');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-restricted-property-modifications', rule, {\n  valid: [\n    // ****************************************\n    // Test cases for computed property macros.\n    // ****************************************\n\n    // readOnly\n    {\n      code: \"import {readOnly} from '@ember/object/computed'; readOnly('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {readOnly} from '@ember/object/computed'; readOnly('currentUser.isUS')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {computed} from '@ember/object'; computed.readOnly('currentUser.isUS')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n\n    // alias\n    {\n      code: \"import {alias} from '@ember/object/computed'; alias('myProperty')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {alias} from '@ember/object/computed'; alias('myProperty.otherThing')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {computed} from '@ember/object'; computed.alias('myProperty')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n\n    // reads\n    {\n      code: \"import {reads} from '@ember/object/computed'; reads('myProperty')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {reads} from '@ember/object/computed'; reads('myProperty.otherThing')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {computed} from '@ember/object'; computed.reads('myProperty')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n\n    // Without import\n    {\n      code: \"alias('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"reads('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"computed.alias('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"computed.reads('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {computed} from 'wrong-import'; computed.reads('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"import {reads} from '@ember/object/computed'; computed.reads('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n\n    // ****************************************\n    // Test cases for this.set(...);\n    // ****************************************\n    {\n      code: \"this.get('currentUser.somePermission')\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"something.set('currentUser.somePermission', true)\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"this.set('someProperty.somePermission', true)\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"this.set('someProperty.currentUser', true)\",\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: \"this.set('currentUserWithLongerName.somePermission', true)\",\n      options: [{ properties: ['currentUser'] }],\n    },\n\n    // ****************************************\n    // Test cases for assignment\n    // ****************************************\n    {\n      code: 'this.foo = 123;',\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: 'this.foo.bar = 123;',\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: 'this.foo.currentUser = 123;',\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: 'currentUser = 123;',\n      options: [{ properties: ['currentUser'] }],\n    },\n    {\n      code: 'const currentUser = 123;',\n      options: [{ properties: ['currentUser'] }],\n    },\n  ],\n  invalid: [\n    // ****************************************\n    // Test cases for computed property macros.\n    // ****************************************\n    {\n      code: \"import {alias} from '@ember/object/computed'; alias('currentUser')\",\n      output: \"import {alias} from '@ember/object/computed'; readOnly('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'useReadOnlyMacro', type: 'CallExpression' }],\n    },\n    {\n      code: \"import {alias} from '@ember/object/computed'; alias('currentUser.isUS')\",\n      output: \"import {alias} from '@ember/object/computed'; readOnly('currentUser.isUS')\",\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'useReadOnlyMacro', type: 'CallExpression' }],\n    },\n    {\n      code: \"import {computed} from '@ember/object'; computed.alias('currentUser')\",\n      output: \"import {computed} from '@ember/object'; computed.readOnly('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'useReadOnlyMacro', type: 'CallExpression' }],\n    },\n    {\n      code: \"import {reads} from '@ember/object/computed'; reads('currentUser')\",\n      output: \"import {reads} from '@ember/object/computed'; readOnly('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'useReadOnlyMacro', type: 'CallExpression' }],\n    },\n    {\n      code: \"import {reads} from '@ember/object/computed'; reads('currentUser.isUS')\",\n      output: \"import {reads} from '@ember/object/computed'; readOnly('currentUser.isUS')\",\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'useReadOnlyMacro', type: 'CallExpression' }],\n    },\n    {\n      code: \"import {computed} from '@ember/object'; computed.reads('currentUser')\",\n      output: \"import {computed} from '@ember/object'; computed.readOnly('currentUser')\",\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'useReadOnlyMacro', type: 'CallExpression' }],\n    },\n\n    // ****************************************\n    // Test cases for this.set(...);\n    // ****************************************\n    {\n      code: \"this.set('currentUser', {})\",\n      output: null,\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'doNotUseSet', type: 'CallExpression' }],\n    },\n    {\n      code: \"this.set('currentUser.somePermission', true)\",\n      output: null,\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'doNotUseSet', type: 'CallExpression' }],\n    },\n\n    // ****************************************\n    // Test cases for assignment\n    // ****************************************\n    {\n      code: 'this.currentUser = {};',\n      output: null,\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'doNotUseAssignment', type: 'AssignmentExpression' }],\n    },\n    {\n      code: 'this.currentUser.foo = true;',\n      output: null,\n      options: [{ properties: ['currentUser'] }],\n      errors: [{ messageId: 'doNotUseAssignment', type: 'AssignmentExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-restricted-resolver-tests.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-restricted-resolver-tests');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGES } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('no-restricted-resolver-tests', rule, {\n  valid: [\n    `\n    import { moduleFor } from 'ember-qunit';\n    moduleFor('service:session', {\n      ...foo,\n      integration: true\n    });\n    `,\n    `\n    import { moduleForComponent } from 'ember-qunit';\n    moduleForComponent('display-page', {\n      integration: true\n    });\n    `,\n    `\n    import { moduleForModel } from 'ember-qunit';\n    moduleForModel('post', {\n      integration: true\n    });\n    `,\n    `\n    import { setupTest } from 'ember-qunit';\n    setupTest('service:session', {\n      integration: true\n    });\n    `,\n    `\n    import { setupComponentTest } from 'ember-mocha';\n    setupComponentTest('display-page', {\n      integration: true\n    });\n    `,\n    `\n    import { setupModelTest } from 'ember-mocha';\n    setupModelTest('post', {\n      integration: true\n    });\n    `,\n    `\n    module('foo', function(hooks) {\n      setupTest(hooks);\n    });\n    `,\n    \"import { setupTest } from 'ember-qunit';\",\n    `\n    const setupTest = require('ember-fastboot-addon-tests').setupTest;\n\n    describe('Integration tests', function() {\n      setupTest('fastboot-ready-app');\n    });\n    `,\n  ],\n  invalid: [\n    {\n      code: `import { moduleFor } from 'ember-qunit';\n\n              moduleFor('service:session');\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getSingleStringArgumentMessage('moduleFor'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleFor } from 'ember-qunit';\n\n              moduleFor('service:session', {\n                unit: true\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoUnitTrueMessage('moduleFor'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleFor } from 'ember-qunit';\n\n              moduleFor('service:session', {\n                needs: ['type:thing']\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoNeedsMessage('moduleFor'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleFor } from 'ember-qunit';\n\n              moduleFor('service:session', arg2, {});\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoPOJOWithoutIntegrationTrueMessage('moduleFor'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForComponent } from 'ember-qunit';\n\n              moduleForComponent('display-page');\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getSingleStringArgumentMessage('moduleForComponent'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForComponent } from 'ember-qunit';\n\n              moduleForComponent('display-page', {\n                unit: true\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoUnitTrueMessage('moduleForComponent'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForComponent } from 'ember-qunit';\n\n              moduleForComponent('display-page', {\n                needs: ['type:thing']\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoNeedsMessage('moduleForComponent'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForComponent } from 'ember-qunit';\n\n              moduleForComponent('display-page', arg2, {});\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoPOJOWithoutIntegrationTrueMessage('moduleForComponent'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForModel } from 'ember-qunit';\n\n              moduleForModel('post');\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getSingleStringArgumentMessage('moduleForModel'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForModel } from 'ember-qunit';\n\n              moduleForModel('post', {\n                unit: true\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoUnitTrueMessage('moduleForModel'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForModel } from 'ember-qunit';\n\n              moduleForModel('post', {\n                needs: ['type:thing']\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoNeedsMessage('moduleForModel'),\n        },\n      ],\n    },\n    {\n      code: `import { moduleForModel } from 'ember-qunit';\n\n              moduleForModel('post', arg2, {});\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoPOJOWithoutIntegrationTrueMessage('moduleForModel'),\n        },\n      ],\n    },\n    {\n      code: `import { setupTest } from 'ember-mocha';\n\n              setupTest('service:session');\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getSingleStringArgumentMessage('setupTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupTest } from 'ember-mocha';\n\n              setupTest('service:session', {\n                unit: true\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoUnitTrueMessage('setupTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupTest } from 'ember-mocha';\n\n              setupTest('service:session', {\n                needs: ['type:thing']\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoNeedsMessage('setupTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupTest } from 'ember-mocha';\n\n              setupTest('service:session', arg2, {});\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoPOJOWithoutIntegrationTrueMessage('setupTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupComponentTest } from 'ember-mocha';\n\n              setupComponentTest('display-page');\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getSingleStringArgumentMessage('setupComponentTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupComponentTest } from 'ember-mocha';\n\n              setupComponentTest('display-page', {\n                unit: true\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoUnitTrueMessage('setupComponentTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupComponentTest } from 'ember-mocha';\n\n              setupComponentTest('display-page', {\n                needs: ['type:thing']\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoNeedsMessage('setupComponentTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupComponentTest } from 'ember-mocha';\n\n              setupComponentTest('display-page', arg2, {});\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoPOJOWithoutIntegrationTrueMessage('setupComponentTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupModelTest } from 'ember-mocha';\n\n              setupModelTest('post');\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getSingleStringArgumentMessage('setupModelTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupModelTest } from 'ember-mocha';\n\n              setupModelTest('post', {\n                unit: true\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoUnitTrueMessage('setupModelTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupModelTest } from 'ember-mocha';\n\n              setupModelTest('post', {\n                needs: ['type:thing']\n              });\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoNeedsMessage('setupModelTest'),\n        },\n      ],\n    },\n    {\n      code: `import { setupModelTest } from 'ember-mocha';\n\n              setupModelTest('post', arg2, {});\n            `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGES.getNoPOJOWithoutIntegrationTrueMessage('setupModelTest'),\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-restricted-service-injections.js",
    "content": "const RuleTester = require('eslint').RuleTester;\nconst rule = require('../../../lib/rules/no-restricted-service-injections');\n\nconst { DEFAULT_ERROR_MESSAGE } = rule;\n\nconst EMBER_IMPORT = \"import Ember from 'ember';\";\nconst INJECT_IMPORT = \"import {inject} from '@ember/service';\";\nconst SERVICE_IMPORT = \"import {inject as service} from '@ember/service';\";\nconst NEW_SERVICE_IMPORT = \"import {service} from '@ember/service';\";\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-restricted-service-injections', rule, {\n  valid: [\n    {\n      // Service name doesn't match (with property name):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service name doesn't match (with property name):\n      filename: 'app/components/path.js',\n      code: `${INJECT_IMPORT} Component.extend({ myService: inject() })`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service name doesn't match (with property name):\n      filename: 'app/components/path.js',\n      code: `${EMBER_IMPORT} Component.extend({ myService: Ember.inject.service() })`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service name doesn't match (with string argument):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ randomName: service('myService') })`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service name doesn't match (with string argument):\n      filename: 'app/components/path.js',\n      code: `${EMBER_IMPORT} Component.extend({ randomName: Ember.inject.service('myService') })`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service name doesn't match (with decorator)\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} class MyComponent extends Component { @service('myService') randomName }`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service name doesn't match (with decorator and new import)\n      filename: 'app/components/path.js',\n      code: `${NEW_SERVICE_IMPORT} class MyComponent extends Component { @service('myService') randomName }`,\n      options: [{ paths: ['app/components'], services: ['abc'] }],\n    },\n    {\n      // Service scope doesn't match:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ randomName: service('scope/myService') })`,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n    },\n    {\n      // File path doesn't match:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      options: [{ paths: ['other/path'], services: ['my-service'] }],\n    },\n    {\n      // Not the service decorator:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: otherDecorator() })`,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n    },\n    {\n      // Ignores injection due to dynamic variable usage:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service(SOME_VARIABLE) })`,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n    },\n    {\n      // TODO: This should be invalid. With `inject.service` decorator.\n      filename: 'app/components/path.js',\n      code: `${INJECT_IMPORT} class MyComponent extends Component { @inject.service('myService') randomName }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n    },\n  ],\n  invalid: [\n    {\n      // Without service name argument:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // Without service name argument and new import:\n      filename: 'app/components/path.js',\n      code: `${NEW_SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // Without service name argument (property name as string literal):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ 'myService': service() })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With camelized service name argument:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ randomName: service('myService') })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With dasherized service name argument:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ randomName: service('my-service') })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With nested, camelized service name:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ randomName: service('scope/myService') })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['scope/my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With nested, dasherized service name:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ randomName: service('scope/my-service') })`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['scope/my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With decorator with camelized service name argument:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} class MyComponent extends Component { @service('myService') randomName }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With decorator with camelized service name argument (inject):\n      filename: 'app/components/path.js',\n      code: `${INJECT_IMPORT} class MyComponent extends Component { @inject('myService') randomName }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With decorator with dasherized service name argument:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} class MyComponent extends Component { @service('my-service') randomName }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With decorator without service name argument (without parentheses):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} class MyComponent extends Component { @service myService }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With decorator without service name argument (with parentheses):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} class MyComponent extends Component { @service() myService }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With decorator without service name argument (with parentheses) (with property name as string literal):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} class MyComponent extends Component { @service() 'myService' }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With chained decorator\n      filename: 'app/components/path.js',\n      code: `${EMBER_IMPORT} class MyComponent extends Component { @Ember.inject.service('myService') randomName }`,\n      output: null,\n      options: [{ paths: ['app/components'], services: ['my-service'] }],\n      errors: [\n        {\n          message: DEFAULT_ERROR_MESSAGE,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      // With custom error message:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      output: null,\n      options: [\n        {\n          paths: ['app/components'],\n          services: ['my-service'],\n          message: 'my-service is deprecated, please do not use it.',\n        },\n      ],\n      errors: [{ message: 'my-service is deprecated, please do not use it.', type: 'Property' }],\n    },\n    {\n      // With multiple violations:\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      output: null,\n      options: [\n        { paths: ['app/components'], services: ['my-service'], message: 'Error 1' },\n        { paths: ['app/components'], services: ['my-service'], message: 'Error 2' },\n      ],\n      errors: [\n        { message: 'Error 1', type: 'Property' },\n        { message: 'Error 2', type: 'Property' },\n      ],\n    },\n    {\n      // Without specifying any paths (should match any path):\n      filename: 'app/components/path.js',\n      code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,\n      output: null,\n      options: [{ services: ['my-service'] }],\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],\n    },\n\n    {\n      // TypeScript annotated injection (TypeScript).\n      code: `\n      import Route from '@ember/routing/route';\n      ${INJECT_IMPORT}\n      import type Store from '@ember-data/store';\n      export default class ApplicationRoute extends Route {\n        @inject declare store: Store;\n      }\n      `,\n      output: null,\n      options: [{ services: ['store'] }],\n      parser: require.resolve('@typescript-eslint/parser'),\n      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'PropertyDefinition' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-runloop.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-runloop');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('no-runloop', rule, {\n  valid: [\n    `\n      run();\n      later();\n    `,\n    `\n      import { run } from 'foobar';\n      run();\n    `,\n    `\n      import { run } from '@ember/runloop';\n      this.run();\n      runTask();\n      runRun();\n    `,\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run();\n        later();\n      `,\n      options: [{ allowList: ['run'] }],\n    },\n    {\n      code: `\n        import { run as foobar } from '@ember/runloop';\n        foobar();\n      `,\n      options: [{ allowList: ['run'] }],\n    },\n    `\n      import run from '@ember/runloop';\n      run();\n    `,\n    `\n      import { run } from '@ember/runloop';\n      run.run();\n      run.foobar();\n    `,\n    `\n      import { later } from '@ember/runloop';\n      later.run();\n      later.foobar();\n    `,\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.later();\n      `,\n      options: [{ allowList: ['later'] }],\n    },\n    `\n      function hasOwnProperty() {};\n      hasOwnProperty();\n      \n      function isPrototypeOf() {};\n      isPrototypeOf();\n      \n      function propertyIsEnumerable() {};\n      propertyIsEnumerable();\n      \n      function toLocaleString() {};\n      toLocaleString();\n      \n      function toString() {};\n      toString();\n      \n      function valueOf() {};\n      valueOf();\n      \n      function constructor() {};\n      constructor();\n    `,\n    `\n      import { hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString, toString, valueOf, constructor } from './util';\n      \n      hasOwnProperty();\n      isPrototypeOf();\n      propertyIsEnumerable();\n      toLocaleString();\n      toString();\n      valueOf();\n      constructor();\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { run as foobar } from '@ember/runloop';\n        foobar();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { later } from '@ember/runloop';\n        later();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'lifelineReplacement',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { later as foobar } from '@ember/runloop';\n        foobar();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'lifelineReplacement',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { run, later } from '@ember/runloop';\n        run();\n        later();\n      `,\n      output: null,\n      options: [{ allowList: ['run'] }],\n      errors: [\n        {\n          messageId: 'lifelineReplacement',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { run, later } from '@ember/runloop';\n        run();\n        later();\n      `,\n      output: null,\n      options: [{ allowList: ['later'] }],\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // chaining off of `run`\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.later();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'lifelineReplacement',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { run } from '@ember/runloop';\n        run.begin();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { run as foobar } from '@ember/runloop';\n        foobar.schedule();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'lifelineReplacement',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import * as run from '@ember/runloop';\n        run.later();\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'lifelineReplacement',\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-settled-after-test-helper.js",
    "content": "const RuleTester = require('eslint').RuleTester;\nconst rule = require('../../../lib/rules/no-settled-after-test-helper');\n\nconst { ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-settled-after-test-helper', rule, {\n  valid: [\n    `\n      import { waitFor, settled } from '@ember/test-helpers';\n\n      async () => {\n        await waitFor('.foo');\n        await settled();\n      }\n    `,\n    `\n      import { waitFor, settled } from '@ember/test-helpers';\n\n      async () => {\n        await waitFor('.foo');\n        await settled;\n      }\n    `,\n    `\n      import { settled } from '@ember/test-helpers';\n\n      async () => {\n        await settled();\n      }\n    `,\n    `\n      import { settled } from '@ember/test-helpers';\n\n      async () => {\n        this.set('foo', 42);\n        await settled();\n      }\n    `,\n    `\n      import { settled } from '@ember/test-helpers';\n\n      async () => {\n        let promise = waitFor('.foo');\n        await promise;\n        await settled();\n      }\n    `,\n    `\n      import { settled } from '@ember/test-helpers';\n\n      function click() {}\n\n      async () => {\n        await click('.foo');\n        await settled();\n      }\n    `,\n    `\n      import { settled } from '@ember/test-helpers';\n\n      export async function visit(url) {\n        try {\n          something();\n        } catch {}\n\n        await settled();\n      }\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n        import { click, settled } from '@ember/test-helpers';\n\n        async () => {\n          await click('.foo');\n          await settled();\n        }\n      `,\n      output: `\n        import { click, settled } from '@ember/test-helpers';\n\n        async () => {\n          await click('.foo');\n\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\n        }\n      `,\n      errors: [{ message: ERROR_MESSAGE, line: 6, column: 11, endLine: 6, endColumn: 27 }],\n    },\n    {\n      code: `\n        import { fillIn, settled } from '@ember/test-helpers';\n\n        async () => {\n          await fillIn('.foo');\n          await settled();\n        }\n      `,\n      output: `\n        import { fillIn, settled } from '@ember/test-helpers';\n\n        async () => {\n          await fillIn('.foo');\n\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\n        }\n      `,\n      errors: [{ message: ERROR_MESSAGE, line: 6, column: 11, endLine: 6, endColumn: 27 }],\n    },\n    {\n      code: `\n        import { settled } from '@ember/test-helpers';\n\n        async () => {\n          await settled();\n          await settled();\n        }\n      `,\n      output: `\n        import { settled } from '@ember/test-helpers';\n\n        async () => {\n          await settled();\n\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\n        }\n      `,\n      errors: [{ message: ERROR_MESSAGE, line: 6, column: 11, endLine: 6, endColumn: 27 }],\n    },\n    {\n      code: `\n        import { click as cliiiick, settled } from '@ember/test-helpers';\n\n        async () => {\n          await cliiiick('.foo');\n          await settled();\n        }\n      `,\n      output: `\n        import { click as cliiiick, settled } from '@ember/test-helpers';\n\n        async () => {\n          await cliiiick('.foo');\n\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\\u0020\n        }\n      `,\n      errors: [{ message: ERROR_MESSAGE, line: 6, column: 11, endLine: 6, endColumn: 27 }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-shadow-route-definition.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-shadow-route-definition');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { buildErrorMessage } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-shadow-route-definition', rule, {\n  valid: [\n    'this.route(\"blog\");',\n    'this.route(\"blog\", function() {});',\n    'this.route(\"blog\", { ...foo });',\n    'this.route(\"blog\", { path: undefined });',\n    'this.route(\"blog\", { path: \"\" });',\n    'this.route(\"blog\", { path: \"/\" });',\n    'this.route(\"blog\", { path: \"blog-posts\" });',\n    'this.route(\"blog\", { path: \"blog-posts\" }, function() {});',\n    'this.route(\"blog\", { path: \"/blog-posts\" });',\n    'this.route(\"blog\", { path: \"blog-posts\", otherOption: true });',\n    `this.route(\"blog\", { path: \"/\" }, function() {\n      this.route(\"sub-blog\", { path: \"/\" });\n    });`,\n    `this.route(\"blog\", function() {\n      this.route(\"post\");\n    });`,\n    `this.route(\"blog\", { path: \"/\" }, function() {\n      this.route(\"post\");\n    });`,\n    `this.route(\"blog\", { path: \"/\" }, function() {\n      this.route(\"post\", { path: \"hero\" });\n    });`,\n    `this.route(\"blog\", function() {\n      this.route(\"post\", { path: \"hero\" });\n    });`,\n    `this.route(\"blog\", { path: \"/blog/post\" }, function() {\n      this.route(\"post\", { path: \"/\" });\n    });\n    this.route(\"post\")`,\n    `this.route(\"blog\", function() {\n      this.route(\"post\", { path: \"/\" });\n    });\n    this.route(\"post\")`,\n\n    // With dynamic segment:\n    'this.route(\"blog\", { path: \":blog\" });',\n    'this.route(\"blog\", { path: \"/:blog\" });',\n    'this.route(\"blog\", { path: \"blog/:blog_id\" });',\n\n    // With dynamic segment, nested:\n    `this.route(\"blog\", { path: \"blog/:blog_id\" }, function() {\n      this.route(\"post\");\n    });\n    this.route(\"post\");`,\n    `this.route(\"blog\", { path: \"/:blog\" }, function() {\n      this.route(\"post\");\n    });\n    this.route(\"post\");`,\n    `this.route(\"blog\", { path: \":blog\" }, function() {\n      this.route(\"post\");\n    });\n    this.route(\"post\");`,\n\n    // With wildcard segment:\n    'this.route(\"blog\", { path: \"*blog\" });',\n    'this.route(\"blog\", { path: \"/*blog\" });',\n    'this.route(\"blog\", { path: \"blog/*blog\" });',\n\n    // With wildcard segment, nested:\n    `this.route(\"blog\", { path: \"*blog\" }, function() {\n      this.route(\"post\");\n    });\n    this.route(\"post\");`,\n    `this.route(\"blog\", { path: \"/*blog\" }, function() {\n      this.route(\"post\");\n    });\n    this.route(\"post\");`,\n    `this.route(\"blog\", { path: \"blog/*blog\" }, function() {\n      this.route(\"post\");\n    });\n    this.route(\"post\");`,\n    // Test nested routes without leading slash (/) in path configuration.\n    `this.route('edit', { path: ':experienceId' }, function () {\n      this.route('views', { path: 'views/:viewId' });\n      this.route('viewcollections', { path: 'viewcollections/:viewCollectionId' });\n    });`,\n    `this.route('post', { path: '/:post' });\n    this.route('edit', { path: '/:post/edit' });`,\n\n    // With dynamic/variable route or path name:\n    'this.route(someVariable);',\n    \"this.route('views', { path: someVariable })\",\n    `this.route(someVariable, function () {\n      this.route('route');\n    });\n    `,\n    `this.route('first', { path: foo });\n    this.route('second', { path: 'foo' });\n    `,\n    `this.route(foo);\n    this.route('foo');\n    `,\n    'this.route(null)',\n    'this.route(true)',\n    // Test not crashing on unexpected values, we bail out\n    'this.route({})',\n    `this.route(getRouteName(), function () {\n      this.route('route');\n    });`,\n    `this.route(\"my-route\", { path: getRoutePath() }, function () {\n      this.route('route');\n    });`,\n\n    // Test if else conditions for route definitions\n    `if (this.options.getTreatmentIsEnabled('test-key')) {\n      this.route('new-route', { path: '/test' });\n    } else {\n      this.route('old-route', { path: '/test' });\n    }`,\n\n    `if (this.options.getTreatmentIsEnabled('test-key')) {\n      this.route('new-route', { path: '/test' });\n    } else {\n      if (false) {\n        this.route('old-route', { path: '/test' });\n      } else {\n        this.route('old-route-v2', { path: '/test' });\n      }\n    }`,\n\n    `if (this.options.getTreatmentIsEnabled('test-key')) {\n      if (true) {\n        this.route('new-route', { path: '/test' });\n      } else {\n        this.route('new-route-v2', { path: '/test' });\n      }\n    } else {\n      if (false) {\n        this.route('old-route', { path: '/test' });\n      } else {\n        this.route('old-route-v2', { path: '/test' });\n      }\n    }`,\n\n    // Not Ember's route function:\n    'test();',\n    \"test('blog');\",\n    \"test('blog', { path: 'blog' });\",\n    \"test('blog', { path: '/blog' });\",\n    \"this.test('blog');\",\n    \"this.test('blog', { path: 'blog' });\",\n    \"this.test('blog', { path: '/blog' });\",\n    \"MyClass.route('blog');\",\n    \"MyClass.route('blog', { path: 'blog' });\",\n    \"MyClass.route('blog', { path: '/blog' });\",\n    \"route.unrelatedFunction('blog', { path: 'blog' });\",\n    \"this.route.unrelatedFunction('blog', { path: 'blog' });\",\n    {\n      filename: 'my-test.js',\n      code: `\n        this.route(\"blog\");\n        this.route(\"blog\");\n      `,\n    },\n  ],\n  invalid: [\n    {\n      code: `\n        this.route('post', { path: '' });\n        this.route('edit', { path: '/' });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'edit',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: '',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route('post', { path: ' ' });\n        this.route('edit', { path: ' / ' });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'edit',\n              fullPath: ' / ',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: ' ',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\");\n        this.route(\"blog\");\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: 'blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: 'blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \"/\" }, function() {\n          this.route(\"post\");\n        });\n        this.route(\"post\");\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'post',\n              fullPath: 'post',\n              source: {\n                loc: {\n                  start: {\n                    line: 5,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: '/post',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \"/\" }, function() {\n          this.route(\"post\", function() {\n            this.route(\"author\");\n          });\n        });\n        this.route(\"post\");\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'post',\n              fullPath: 'post',\n              source: {\n                loc: {\n                  start: {\n                    line: 7,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: '/post',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \"/\" }, function() {\n          this.route(\"sub-blog\", { path: \"/\" }, function() {\n            this.route(\"post\");\n          });\n        });\n        this.route(\"post\");\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'post',\n              fullPath: 'post',\n              source: {\n                loc: {\n                  start: {\n                    line: 7,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: '/post',\n              source: {\n                loc: {\n                  start: {\n                    line: 4,\n                    column: 12,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \"/\" }, function() {\n          this.route(\"sub-blog\", { path: \"/\" }, function() {\n            this.route(\"post\");\n          });\n        });\n        this.route(\"sub-blog\", { path: \"/\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'sub-blog',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 7,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \"/\" }, function() {\n          this.route(\"sub-blog\", { path: \"/\" }, function() {\n            this.route(\"post\");\n          });\n        });\n        this.route(\"sub-blog\", { path: \"/\" }, function() {\n          this.route(\"post\");\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'sub-blog',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 7,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'post',\n              fullPath: '/post',\n              source: {\n                loc: {\n                  start: {\n                    line: 8,\n                    column: 10,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: '/post',\n              source: {\n                loc: {\n                  start: {\n                    line: 4,\n                    column: 12,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \"*blog\" });\n        this.route(\"blog\", { path: \"/*blog\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/*blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: '*blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"main\", { path: \"/\" }, function() {\n          this.route(\"blog\", { path: \"*blog\" });\n        });\n        this.route(\"blog\", { path: \"/*blog\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/*blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 5,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: '/*blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"main\", { path: \" / \" }, function() {\n          this.route(\"blog\", { path: \" *blog \" });\n        });\n        this.route(\"blog\", { path: \"/*blog\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/*blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 5,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: ' /  *blog ',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"main\", { path: \"/\" }, function() {\n          this.route(\"blog\", { path: \"*blog\" });\n        });\n        this.route(\"blog\", { path: \"/*anotherWildcard\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/*anotherWildcard',\n              source: {\n                loc: {\n                  start: {\n                    line: 5,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: '/*blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \":blog\" });\n        this.route(\"blog\", { path: \"/:blog\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/:blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: ':blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"blog\", { path: \":blog\" });\n        this.route(\"blog\", { path: \"/:anotherParamName\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/:anotherParamName',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: ':blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"main\", { path: \"/\" }, function() {\n          this.route(\"blog\", { path: \":blog\" });\n        });\n        this.route(\"blog\", { path: \"/:blog\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'blog',\n              fullPath: '/:blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 5,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'blog',\n              fullPath: '/:blog',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"first\", { path: \"/\" });\n        this.route(\"second\", { path: \"/\" });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'second',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'first',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(\"main\", function() {\n          this.route(\"first\", { path: \"/\" });\n          this.route(\"second\", { path: \"/\" });\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'second',\n              fullPath: 'main/',\n              source: {\n                loc: {\n                  start: {\n                    line: 4,\n                    column: 10,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'first',\n              fullPath: 'main/',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(someVariable, function() {\n          this.route(\"first\", { path: \"/\" });\n          this.route(\"second\", { path: \"/\" });\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'second',\n              fullPath: 'someVariable/',\n              source: {\n                loc: {\n                  start: {\n                    line: 4,\n                    column: 10,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'first',\n              fullPath: 'someVariable/',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 10,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(someVariable);\n        this.route(someVariable);\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'someVariable',\n              fullPath: 'someVariable',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'someVariable',\n              fullPath: 'someVariable',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route('first', { path: someVariable });\n        this.route('second', { path: someVariable });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'second',\n              fullPath: 'someVariable',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'first',\n              fullPath: 'someVariable',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        this.route(null, { path: someVariable });\n        this.route(null, { path: someVariable });\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'null',\n              fullPath: 'someVariable',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 8,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'null',\n              fullPath: 'someVariable',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 8,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    {\n      code: `\n        const options1 = { path: '/' }; this.route('post', options1);\n        const options2 = { path: '/' }; this.route('edit', options2);\n      `,\n      output: null,\n      errors: [\n        {\n          message: buildErrorMessage({\n            leftRoute: {\n              name: 'edit',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 3,\n                    column: 40,\n                  },\n                },\n              },\n            },\n            rightRoute: {\n              name: 'post',\n              fullPath: '/',\n              source: {\n                loc: {\n                  start: {\n                    line: 2,\n                    column: 40,\n                  },\n                },\n              },\n            },\n          }),\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n\ndescribe('no-shadow-route-definition', () => {\n  it('builds error message string', () => {\n    const message = buildErrorMessage({\n      leftRoute: {\n        name: 'second',\n        fullPath: 'main/',\n        source: {\n          loc: {\n            start: {\n              line: 4,\n              column: 10,\n            },\n          },\n        },\n      },\n      rightRoute: {\n        name: 'first',\n        fullPath: 'main/',\n        source: {\n          loc: {\n            start: {\n              line: 3,\n              column: 10,\n            },\n          },\n        },\n      },\n    });\n\n    expect(message).toBe(\n      'Route \"second\" (\"main/\", 4L:10C) is shadowing route \"first\" (\"main/\", 3L:10C)'\n    );\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/no-side-effects.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-side-effects');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\nconst { ERROR_MESSAGE } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('no-side-effects', rule, {\n  valid: [\n    'alias(\"test.length\")',\n    'computed(function() { this.test; })',\n    'computed(function() { this.myFunction(); })',\n    'computed(function() { class Foo { @someDecorator() someProp } })', // Does not throw with node type (ClassProperty) not handled by estraverse.\n\n    // set\n    'computed(function() { foo.set(this, \"testAmount\", test.length); return \"\"; });',\n    \"computed(function() { foo1.foo2.set('bar', 123); })\",\n    'import { set } from \"@ember/object\"; computed(function() { set(foo, 123); });', // imported set\n    'import Ember from \"ember\"; computed(function() { Ember.set(foo, 123); });', // Ember.set\n    'computed(function() { let x = 123; x = 456; someObject.x = 123; })', // ES5 setter\n\n    // Inside setter\n    'import { set } from \"@ember/object\"; computed({ get() { return \"\"; }, set() { set(this, \"testAmount\", test.length); }})',\n    'computed({ get() { return \"\"; }, set() { setProperties(); }})',\n\n    // setProperties\n    'computed(function() { foo.setProperties(\"bar\", 123); return \"\"; });',\n    'import { setProperties } from \"@ember/object\"; computed(function() { setProperties(foo, 123); return \"\"; });', // imported setProperties\n    'import Ember from \"ember\"; computed(function() { Ember.setProperties(foo, 123); });', // Ember.setProperties\n\n    // Decorators:\n    {\n      code: `\n        class Test {\n          @computed('first', 'last')\n          get fullName() { return this.first + ' ' + this.last; }\n        }\n      `,\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n    {\n      code: `\n        class Test {\n          @computed\n          get fullName() { return 'foo'; }\n        }\n      `,\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n\n    // No computed property function body;\n    'computed()',\n    'computed(\"test\")',\n    'computed(function() {})',\n    'computed',\n\n    // Not in a computed property:\n    \"this.set('x', 123);\",\n    \"import { set } from '@ember/object'; set(this, 'x', 123);\",\n    'this.setProperties({ x: 123 });',\n    \"import { setProperties } from '@ember/object'; setProperties(this, 'x', 123);\",\n    \"import Ember from 'ember'; Ember.set(this, 'x', 123);\",\n    \"import Ember from 'ember'; Ember.setProperties(this, 'x', 123);\",\n    'this.x = 123;',\n    'this.x.y = 123;',\n\n    // Events (but `catchEvents` option off):\n    { code: 'computed(function() { this.send(); })', options: [{ catchEvents: false }] },\n    { code: 'computed(function() { this.sendAction(); })', options: [{ catchEvents: false }] },\n    { code: 'computed(function() { this.sendEvent(); })', options: [{ catchEvents: false }] },\n    { code: 'computed(function() { this.trigger(); })', options: [{ catchEvents: false }] },\n    {\n      code: 'import { sendEvent } from \"@ember/object/events\"; computed(function() { sendEvent(); })',\n      options: [{ catchEvents: false }],\n    },\n\n    // Not in a computed property (events):\n    'this.send()',\n    'this.sendAction()',\n    'this.sendEvent()',\n    'this.trigger()',\n    'import { sendEvent } from \"@ember/object/events\"; sendEvent();',\n\n    // checkPlainGetters = false\n    {\n      code: 'import Component from \"@ember/component\"; class Foo extends Component { get foo() { this.x = 123; } }',\n      options: [{ checkPlainGetters: false }],\n    },\n\n    // checkPlainGetters = true\n    {\n      code: 'import Component from \"@ember/component\"; class Foo extends Component { get foo() { return 123; } }',\n      options: [{ checkPlainGetters: true }],\n    },\n    {\n      code: 'import Component from \"@ember/component\"; class Foo extends Component { set foo(x) { this.x = x; } }',\n      options: [{ checkPlainGetters: true }],\n    },\n    {\n      // Not Ember class:\n      code: 'class Foo { get foo() { this.x = x; } }',\n      options: [{ checkPlainGetters: true }],\n    },\n  ].map(addComputedImport),\n  invalid: [\n    // this.set\n    {\n      code: 'computed(function() {this.set(\"testAmount\", test.length); return \"\";})',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { this.setProperties(\"testAmount\", test.length); return \"\";})',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    // imported set\n    {\n      code: 'import { set } from \"@ember/object\"; computed(function() { set(this, 123); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import { set } from \"@ember/object\"; computed(function() { set(this.foo, 123); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import { setProperties } from \"@ember/object\"; computed(function() { setProperties(this, \"foo\", 123); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import { setProperties } from \"@ember/object\"; computed(function() { setProperties(this.foo, 123); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    // Ember.set\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.set(this, \"testAmount\", test.length); return \"\"; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.set(this.foo, \"testAmount\", test.length); return \"\"; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.set(this.foo?.bar, \"testAmount\", test.length); return \"\"; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.setProperties(this, \"testAmount\", test.length); return \"\"; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    // Renamed Ember import\n    {\n      code: 'import E from \"ember\"; computed(function() { E.set(this, \"testAmount\", test.length); return \"\"; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import E from \"ember\"; computed(function() { E.setProperties(this, \"testAmount\", test.length); return \"\"; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    // Decorator with getter inside object parameter:\n    {\n      code: `\n        class Test {\n          @computed('key', {\n            get() {\n              this.set('x', 123);\n            },\n            set(key, value) {}\n          })\n          someProp\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n    // Decorator with getter function:\n    {\n      code: `\n        class Test {\n          @computed()\n          get someProp() { this.set('x', 123); }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n    // Decorator without parentheses:\n    {\n      code: `\n        class Test {\n          @computed\n          get someProp() { this.set('x', 123); }\n        }\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n\n    {\n      // ES5 setter:\n      code: 'computed(function() { this.x = 123; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n\n    {\n      // ES5 setter with nested path:\n      code: 'computed(function() { this.x.y = 123; })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n\n    // Events (from this):\n    {\n      code: 'computed(function() { this.send(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { this.sendAction(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { this.sendEvent(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { this.trigger(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    // Events (from Ember):\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.send(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.sendAction(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.sendEvent(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; computed(function() { Ember.trigger(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    {\n      // Imported sendEvent function:\n      code: 'import { sendEvent as se } from \"@ember/object/events\"; computed(function() { se(); })',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n\n    {\n      // checkPlainGetters = true (default)\n      code: 'import Component from \"@ember/component\"; class Foo extends Component { get foo() { this.x = 123; } }',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n    {\n      // checkPlainGetters = true\n      code: 'import Component from \"@ember/component\"; class Foo extends Component { get foo() { this.x = 123; } }',\n      output: null,\n      options: [{ checkPlainGetters: true }],\n      errors: [{ message: ERROR_MESSAGE, type: 'AssignmentExpression' }],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/no-string-prototype-extensions.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/no-string-prototype-extensions');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('no-string-prototype-extensions', rule, {\n  valid: [\n    \"import { dasherize } from '@ember/string'; dasherize('myString')\",\n    \"import { capitalize } from '@ember/string'; capitalize(someString)\",\n    \"import { htmlSafe } from '@ember/template'; htmlSafe('<b>foo</b>')\",\n    \"someString['foo']()\",\n    'something.foo()',\n    'dasherize.foo()',\n    'this.dasherize()',\n\n    // Still allowed to use directly from Ember:\n    \"import Ember from 'ember'; Ember.String.htmlSafe('foo');\",\n    \"import Ember from 'ember'; Ember.String.dasherize('foo');\",\n  ],\n\n  invalid: [\n    {\n      code: \"'myString'.dasherize()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, column: 12, endColumn: 21 }],\n    },\n    {\n      code: 'someString.capitalize()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, column: 12, endColumn: 22 }],\n    },\n    {\n      code: 'getSomeString().dasherize()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, column: 17, endColumn: 26 }],\n    },\n    {\n      code: \"'<b>foo</b>'.htmlSafe()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, column: 14, endColumn: 22 }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-test-and-then.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-test-and-then');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nconst TEST_FILE_NAME = 'some-test.js';\n\nruleTester.run('no-test-and-then', rule, {\n  valid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: \"run(() => { console.log('Hello World.'); });\",\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'myCustomClass.andThen(myFunction);',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'andThen.otherFunction(myFunction);',\n    },\n    {\n      filename: 'not-a-test-file.js',\n      code: 'andThen(() => { assert.ok(myBool); });',\n    },\n  ],\n  invalid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: 'andThen(() => { assert.ok(myBool); });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-test-import-export.js",
    "content": "const rule = require('../../../lib/rules/no-test-import-export');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\nconst NO_IMPORT_MESSAGE = rule.meta.importMessage;\nconst NO_EXPORT_MESSAGE = rule.meta.exportMessage;\n\nruleTester.run('no-test-import-export', rule, {\n  valid: [\n    `\n        import setupModule from './some-test-helper';\n        import { module, test } from 'qunit';\n\n        module('Acceptance | module', setupModule());\n      `,\n    {\n      filename: 'tests/some-test-helper.js',\n      code: `\n        export function beforeEachSetup(){};\n      `,\n    },\n\n    // Exporting from files in tests/helpers is allowed.\n    {\n      filename: 'tests/helpers/setup-application-test.js',\n      code: 'export default function setupApplicationTest(){};',\n    },\n    {\n      filename: 'tests/helpers/index.js',\n      code: 'export function setupApplicationTest(){};',\n    },\n    {\n      filename: 'my-app-name/tests/helpers/setup-application-test.js',\n      code: 'export default function setupApplicationTest(){};',\n    },\n\n    // Importing anything from tests/helpers is allowed.\n    \"import setupApplicationTest from 'tests/helpers/setup-application-test';\",\n    \"import { setupApplicationTest } from 'tests/helpers';\",\n    \"import setupApplicationTest from 'my-app-name/tests/helpers/setup-application-test';\",\n    \"import { setupApplicationTest } from 'my-app-name/tests/helpers';\",\n\n    // Importing anything from test/helpers is allowed (using relative path)\n    {\n      filename: 'my-app-name/tests/helpers/foo.js',\n      code: \"import setupApplicationTest from './setup-application-test';\",\n    },\n    {\n      filename: 'my-app-name/tests/helpers/nested/foo.js',\n      code: \"import setupApplicationTest from '../setup-application-test';\",\n    },\n\n    // Package imports ending in -test should not be flagged\n    \"import { expectTypeOf } from '@glint/type-test';\",\n    \"import something from 'some-package-test';\",\n\n    // Files ending in -test outside tests/ directory should not be treated as test files (#1218)\n    {\n      filename: 'app/components/student-test.js',\n      code: 'export function setup() {}',\n    },\n    // A tests/ folder inside app/ is not a real test directory\n    {\n      filename: 'app/components/tests/foo-test.js',\n      code: 'export function setup() {}',\n    },\n  ],\n  invalid: [\n    {\n      code: `\n        import setupModule from './some-other-test';\n        import { module, test } from 'qunit';\n\n        module('Acceptance | module', setupModule());\n      `,\n      output: null,\n      errors: [\n        {\n          message: NO_IMPORT_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        import {\n          beforeEachSetup,\n          testName,\n        } from './some-other-test';\n        import { module, test } from 'qunit';\n\n        module('Acceptance | module', beforeEachSetup());\n      `,\n      output: null,\n      errors: [\n        {\n          message: NO_IMPORT_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: `\n        import testModule from '../../test-dir/another-test';\n        import { module, test } from 'qunit';\n\n        module('Acceptance | module', testModule());\n      `,\n      output: null,\n      errors: [\n        {\n          message: NO_IMPORT_MESSAGE,\n        },\n      ],\n    },\n    {\n      // Importing from a test file outside test/helpers is disallowed.\n      filename: 'my-app-name/tests/helpers/foo.js',\n      code: \"import testModule from '../../test-dir/another-test';\",\n      output: null,\n      errors: [{ message: NO_IMPORT_MESSAGE }],\n    },\n    {\n      filename: 'tests/some-test.js',\n      code: `\n        export function beforeEachSetup(){};\n      `,\n      output: null,\n      errors: [\n        {\n          message: NO_EXPORT_MESSAGE,\n        },\n      ],\n    },\n    {\n      filename: 'tests/some-test.js',\n      code: `\n        function beforeEachSetup(){};\n\n        export default {beforeEachSetup};\n      `,\n      output: null,\n      errors: [\n        {\n          message: NO_EXPORT_MESSAGE,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-test-module-for.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-test-module-for');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nconst TEST_FILE_NAME = 'some-test.js';\n\nruleTester.run('no-test-module-for', rule, {\n  valid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: 'module()',\n    },\n    {\n      filename: 'not-a-test-file.js',\n      code: 'moduleFor()',\n    },\n    {\n      filename: 'tests/helpers/something.js',\n      code: 'export default function doSomething() {}',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'somethingRandom.moduleFor()',\n    },\n  ],\n  invalid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: 'moduleFor()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'moduleForComponent()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'moduleForAcceptance()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'moduleForBespoke()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      filename: 'tests/helpers/module-for-acceptance',\n      code: 'export default function moduleForAcceptance() {}',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'FunctionDeclaration' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-test-support-import.js",
    "content": "const rule = require('../../../lib/rules/no-test-support-import');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nconst { ERROR_MESSAGE_NO_IMPORT } = rule;\n\nruleTester.run('no-test-support-import', rule, {\n  valid: [\n    {\n      filename: 'foo/tests/some-test-helper.js',\n      code: `\n      import setupModule from './test-support/some-test-helper';\n      import { module, test } from 'qunit';\n\n      module('Acceptance | module', setupModule());\n    `,\n    },\n    {\n      filename: 'foo/test-support/some-test-helper.js',\n      code: `\n      import setupModule from './test-support/some-test-helper';\n      import { module, test } from 'qunit';\n\n      module('Acceptance | module', setupModule());\n    `,\n    },\n    {\n      filename: 'foo/addon-test-support/some-test-helper.js',\n      code: `\n      import setupModule from './test-support/some-test-helper';\n      import { module, test } from 'qunit';\n\n      module('Acceptance | module', setupModule());\n    `,\n    },\n  ],\n  invalid: [\n    {\n      filename: 'app/routes/index.js',\n      code: `\n        import setupModule from './test-support/some-test-helper';\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_NO_IMPORT,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      filename: '@ember/foo/addon/components/index.js',\n      code: `\n        import setupModule from 'foo/test-support/some-test-helper';\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_NO_IMPORT,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      filename: 'foo/test-support-foo/index.js',\n      code: `\n        import setupModule from 'foo/test-support/some-test-helper';\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_NO_IMPORT,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      filename: 'app/components/index.js',\n      code: `\n        import setupModule from './test-support/some-test-helper';\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE_NO_IMPORT,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-test-this-render.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-test-this-render');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { makeErrorMessage } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst TEST_FILE_NAME = 'some-test.js';\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-test-this-render', rule, {\n  valid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: \"run(() => { console.log('Hello World.'); });\",\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'myCustomClass.render(myFunction);',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'myCustomClass.clearRender(myFunction);',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'render.otherFunction(myFunction);',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'clearRender.otherFunction(myFunction);',\n    },\n    {\n      filename: 'not-a-test-file.js',\n      code: 'async () => { await this.render(); }',\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'this.render.otherFunction()',\n    },\n    {\n      filename: 'not-a-test-file.js',\n      code: 'async () => { await this.clearRender(); }',\n    },\n  ],\n  invalid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: 'async () => { await this.render(); }',\n      output: null,\n      errors: [{ message: makeErrorMessage('render'), type: 'CallExpression' }],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: 'async () => { await this.clearRender(); }',\n      output: null,\n      errors: [{ message: makeErrorMessage('clearRender'), type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-tracked-built-ins.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst emberSourceVersionUtil = require('../../../lib/utils/ember-source-version');\nconst rule = require('../../../lib/rules/no-tracked-built-ins');\nconst RuleTester = require('eslint').RuleTester;\n\nconst parserOptions = { ecmaVersion: 2022, sourceType: 'module' };\n\nconst { ERROR_MESSAGE_IMPORT } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\ndescribe('no-tracked-built-ins', () => {\n  beforeAll(() => {\n    vi.spyOn(emberSourceVersionUtil, 'isEmberSourceVersionAtLeast').mockReturnValue(true);\n  });\n\n  afterAll(() => {\n    vi.restoreAllMocks();\n  });\n\n  const ruleTester = new RuleTester({\n    parserOptions,\n    parser: require.resolve('@babel/eslint-parser'),\n  });\n\n  ruleTester.run('no-tracked-built-ins', rule, {\n    valid: [\n      // Already using @ember/reactive/collections\n      \"import { trackedArray } from '@ember/reactive/collections';\",\n      \"import { trackedObject, trackedMap } from '@ember/reactive/collections';\",\n\n      // Not tracked-built-ins\n      \"import { something } from 'other-package';\",\n      \"import { TrackedArray } from 'some-other-package';\",\n\n      // No import at all\n      'const arr = [];',\n    ],\n\n    invalid: [\n      // Single named import\n      {\n        code: \"import { TrackedArray } from 'tracked-built-ins';\",\n        output: \"import { trackedArray } from '@ember/reactive/collections';\",\n        errors: [{ message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' }],\n      },\n\n      // Multiple named imports\n      {\n        code: \"import { TrackedArray, TrackedObject } from 'tracked-built-ins';\",\n        output: \"import { trackedArray, trackedObject } from '@ember/reactive/collections';\",\n        errors: [{ message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' }],\n      },\n\n      // All tracked imports\n      {\n        code: \"import { TrackedArray, TrackedObject, TrackedMap, TrackedSet, TrackedWeakMap, TrackedWeakSet } from 'tracked-built-ins';\",\n        output:\n          \"import { trackedArray, trackedObject, trackedMap, trackedSet, trackedWeakMap, trackedWeakSet } from '@ember/reactive/collections';\",\n        errors: [{ message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' }],\n      },\n\n      // Aliased import\n      {\n        code: \"import { TrackedArray as TA } from 'tracked-built-ins';\",\n        output: \"import { trackedArray as TA } from '@ember/reactive/collections';\",\n        errors: [{ message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' }],\n      },\n\n      // Default import (no autofix)\n      {\n        code: \"import TrackedBuiltins from 'tracked-built-ins';\",\n        output: null,\n        errors: [{ message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' }],\n      },\n\n      // Default import with named imports (no autofix)\n      {\n        code: \"import TrackedBuiltins, { TrackedArray } from 'tracked-built-ins';\",\n        output: null,\n        errors: [{ message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' }],\n      },\n\n      // new TrackedArray() with import\n      {\n        code: `import { TrackedArray } from 'tracked-built-ins';\nconst arr = new TrackedArray([1, 2, 3]);`,\n        output: `import { trackedArray } from '@ember/reactive/collections';\nconst arr = trackedArray([1, 2, 3]);`,\n        errors: [\n          { message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' },\n          {\n            message:\n              'Use `trackedArray(...)` instead of `new TrackedArray(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n            type: 'NewExpression',\n          },\n        ],\n      },\n\n      // new TrackedObject() with import\n      {\n        code: `import { TrackedObject } from 'tracked-built-ins';\nconst obj = new TrackedObject({ a: 1 });`,\n        output: `import { trackedObject } from '@ember/reactive/collections';\nconst obj = trackedObject({ a: 1 });`,\n        errors: [\n          { message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' },\n          {\n            message:\n              'Use `trackedObject(...)` instead of `new TrackedObject(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n            type: 'NewExpression',\n          },\n        ],\n      },\n\n      // new TrackedMap() with import\n      {\n        code: `import { TrackedMap } from 'tracked-built-ins';\nconst map = new TrackedMap();`,\n        output: `import { trackedMap } from '@ember/reactive/collections';\nconst map = trackedMap();`,\n        errors: [\n          { message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' },\n          {\n            message:\n              'Use `trackedMap(...)` instead of `new TrackedMap(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n            type: 'NewExpression',\n          },\n        ],\n      },\n\n      // Aliased import with new expression\n      {\n        code: `import { TrackedArray as TA } from 'tracked-built-ins';\nconst arr = new TA([1, 2, 3]);`,\n        output: `import { trackedArray as TA } from '@ember/reactive/collections';\nconst arr = TA([1, 2, 3]);`,\n        errors: [\n          { message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' },\n          {\n            message:\n              'Use `trackedArray(...)` instead of `new TA(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n            type: 'NewExpression',\n          },\n        ],\n      },\n\n      // Multiple new expressions\n      {\n        code: `import { TrackedArray, TrackedMap } from 'tracked-built-ins';\nconst arr = new TrackedArray();\nconst map = new TrackedMap();`,\n        output: `import { trackedArray, trackedMap } from '@ember/reactive/collections';\nconst arr = trackedArray();\nconst map = trackedMap();`,\n        errors: [\n          { message: ERROR_MESSAGE_IMPORT, type: 'ImportDeclaration' },\n          {\n            message:\n              'Use `trackedArray(...)` instead of `new TrackedArray(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n            type: 'NewExpression',\n          },\n          {\n            message:\n              'Use `trackedMap(...)` instead of `new TrackedMap(...)`. The `@ember/reactive/collections` utilities do not use `new`.',\n            type: 'NewExpression',\n          },\n        ],\n      },\n    ],\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/no-tracked-properties-from-args.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-tracked-properties-from-args');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-tracked-properties-from-args', rule, {\n  valid: [\n    `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        @someDecorator test = this.args.test\n      }`,\n    `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        @tracked test = this.someValue\n      }`,\n    `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        @tracked test = 7\n      }`,\n    `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        test = 7\n      }`,\n    `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        test = \"test\"\n      }`,\n    `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        @tracked test = this.notArgs.args.test\n      }`,\n    `\n      import { tracked as fooTracked } from '@glimmer/tracking';\n\n      class Test {\n        @fooTracked test = this.notArgs.args.test\n      }\n    `,\n    `\n      import { tracked } from '@glimmer/tracking';\n\n      class Test {\n        @tracked test = this.args2.test\n      }\n    `,\n    `\n      class Test{\n        notInitializedProperty;\n      }\n    `,\n    `\n      import { tracked as fooTracked } from '@glimmer/tracking';\n\n      class Test {\n        someProperty = this.someMethod();\n      }\n    `,\n    // Should not crash on method calls with non-string arguments\n    `\n      import { tracked } from '@glimmer/tracking';\n\n      class Test {\n        @tracked test = this.array.indexOf(1);\n      }\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        @tracked test = this.args;\n      }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `\n      import { tracked } from '@glimmer/tracking'\n\n      class Test {\n        @tracked test = this.args.test;\n      }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `\n      import { tracked as fooTracked } from '@glimmer/tracking';\n\n      class Test {\n        @fooTracked test = this.args.test\n      }\n    `,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-try-invoke.js",
    "content": "const rule = require('../../../lib/rules/no-try-invoke');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nruleTester.run('no-try-invoke', rule, {\n  valid: [\n    \"tryInvoke(this, 'foo');\",\n    \"import { tryInvoke } from '@ember/utils'; foo.tryInvoke(this, 'foo');\",\n    \"import { tryInvoke } from '@ember/utils'; tryInvoke.foo(this, 'foo');\",\n    \"import { tryInvoke } from '@ember/utils'; foo();\",\n  ],\n  invalid: [\n    {\n      code: `\n        import { tryInvoke } from '@ember/utils';\n        tryInvoke(this, 'foo');\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import { tryInvoke, isPresent } from '@ember/utils';\n        tryInvoke(this, 'foo', ['bar']);\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-unnecessary-index-route.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-unnecessary-index-route');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester();\nruleTester.run('no-unnecessary-index-route', rule, {\n  valid: [\n    'this.route(\"blog\");',\n    'this.route(\"blog\", function() {});',\n    'this.route(\"blog\", { path: \"\" });',\n    'this.route(\"blog\", { path: \"/\" });',\n    'this.route(\"blog\", { path: \"/:blog_id\" });',\n    'this.route(\"blog\", { path: \"/*path\" });',\n    'this.route(\"blog\", { path: \"blog-posts\" });',\n    'this.route(\"blog\", { path: \"blog-posts\" }, function() {});',\n\n    // Not Ember's route function:\n    'test();',\n    \"test('index');\",\n    \"test('index', { path: '/' });\",\n    \"this.test('index');\",\n    \"this.test('index', { path: '/' });\",\n    \"MyClass.route('index');\",\n    \"MyClass.route('index', { path: '/' });\",\n    \"route.unrelatedFunction('index');\",\n    \"route.unrelatedFunction('index', { path: '/' });\",\n    \"this.route.unrelatedFunction('index');\",\n    \"this.route.unrelatedFunction('index', { path: '/' });\",\n  ],\n  invalid: [\n    {\n      code: 'this.route(\"index\");',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'this.route(\"index\", { path: \"\" });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'this.route(\"index\", { path: \"/\" });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'this.route(\"index\", { path: \"/index\" });',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n    {\n      code: 'this.route(\"index\", { path: \"/\" }, function() {});',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-unnecessary-route-path-option.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-unnecessary-route-path-option');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({ parser: require.resolve('@babel/eslint-parser') });\nruleTester.run('no-unnecessary-route-path-option', rule, {\n  valid: [\n    'this.route(\"blog\");',\n    'this.route(\"blog\", function() {});',\n    'this.route(\"blog\", { ...foo });',\n    'this.route(\"blog\", { path: undefined });',\n    'this.route(\"blog\", { path: \"\" });',\n    'this.route(\"blog\", { path: \"/\" });',\n    'this.route(\"blog\", { path: \"blog-posts\" });',\n    'this.route(\"blog\", { path: \"blog-posts\" }, function() {});',\n    'this.route(\"blog\", { path: \"/blog-posts\" });',\n    'this.route(\"blog\", { path: \"blog-posts\", otherOption: true });',\n\n    // With dynamic segment:\n    'this.route(\"blog\", { path: \":blog\" });',\n    'this.route(\"blog\", { path: \"/:blog\" });',\n    'this.route(\"blog\", { path: \"blog/:blog_id\" });',\n\n    // With wildcard segment:\n    'this.route(\"blog\", { path: \"*blog\" });',\n    'this.route(\"blog\", { path: \"/*blog\" });',\n    'this.route(\"blog\", { path: \"blog/*blog\" });',\n\n    // Not Ember's route function:\n    'test();',\n    \"test('blog');\",\n    \"test('blog', { path: 'blog' });\",\n    \"test('blog', { path: '/blog' });\",\n    \"this.test('blog');\",\n    \"this.test('blog', { path: 'blog' });\",\n    \"this.test('blog', { path: '/blog' });\",\n    \"MyClass.route('blog');\",\n    \"MyClass.route('blog', { path: 'blog' });\",\n    \"MyClass.route('blog', { path: '/blog' });\",\n    \"route.unrelatedFunction('blog', { path: 'blog' });\",\n    \"this.route.unrelatedFunction('blog', { path: 'blog' });\",\n  ],\n  invalid: [\n    {\n      code: 'this.route(\"blog\", { path: \"blog\" });',\n      output: 'this.route(\"blog\" );',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      code: 'this.route(\"blog\", { path: \"blog\" }, function() {});',\n      output: 'this.route(\"blog\",  function() {});',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      code: 'this.route(\"blog\", { path: \"/blog\" });',\n      output: 'this.route(\"blog\" );',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      code: 'this.route(\"blog\", { path: \"/blog\", otherOption: true });',\n      output: 'this.route(\"blog\", {  otherOption: true });',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      code: 'this.route(\"blog\", { otherOption: true, path: \"/blog\" });',\n      output: 'this.route(\"blog\", { otherOption: true  });',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      code: 'this.route(\"blog\", { firstOption: true, path: \"/blog\", lastOption: true });',\n      output: 'this.route(\"blog\", { firstOption: true,  lastOption: true });',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n\n    {\n      // With object variable: single option.\n      code: 'const options = { path: \"blog\" }; this.route(\"blog\", options);',\n      output: 'const options = {  }; this.route(\"blog\", options);',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With object variable: first option.\n      code: 'const options = { path: \"blog\", somethingElse: true }; this.route(\"blog\", options);',\n      output: 'const options = {  somethingElse: true }; this.route(\"blog\", options);',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With object variable: last option.\n      code: 'const options = { somethingElse: true, path: \"blog\" }; this.route(\"blog\", options);',\n      output: 'const options = { somethingElse: true  }; this.route(\"blog\", options);',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n    {\n      // With object variable: middle option.\n      code: 'const options = { firstOption: true, path: \"blog\", lastOption: true }; this.route(\"blog\", options);',\n      output:\n        'const options = { firstOption: true,  lastOption: true }; this.route(\"blog\", options);',\n      errors: [{ message: ERROR_MESSAGE, type: 'Property' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-unnecessary-service-injection-argument.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-unnecessary-service-injection-argument');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst EMBER_IMPORT = \"import Ember from 'ember';\";\nconst SERVICE_IMPORT = \"import {inject} from '@ember/service';\";\nconst RENAMED_SERVICE_IMPORT = \"import {inject as service} from '@ember/service';\";\nconst NEW_SERVICE_IMPORT = \"import {service} from '@ember/service';\";\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\nruleTester.run('no-unnecessary-service-injection-argument', rule, {\n  valid: [\n    // No argument:\n    `${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service() });`,\n    `${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service() });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: inject() });`,\n    `${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ serviceName: service() });`,\n    {\n      code: `${RENAMED_SERVICE_IMPORT} class Test { @service serviceName }`,\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n    `${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,\n\n    // Property name matches service name but service name uses dashes\n    // (allowed because it avoids needless runtime camelization <-> dasherization in the resolver):\n    `${EMBER_IMPORT} export default Component.extend({ specialName: Ember.inject.service('service-name') });`,\n    `${RENAMED_SERVICE_IMPORT} export default Component.extend({ specialName: service('service-name') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ specialName: inject('service-name') });`,\n    `${SERVICE_IMPORT} export default Component.extend({ 'specialName': inject('service-name') });`,\n    `${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ serviceName: service('service-name') });`,\n    `${RENAMED_SERVICE_IMPORT} class Test { @service(\"service-name\") serviceName }`,\n    `${RENAMED_SERVICE_IMPORT} class Test { @service(\"service-name\") 'serviceName' }`,\n    `${NEW_SERVICE_IMPORT} class Test { @service(\"service-name\") 'serviceName' }`,\n\n    // Property name does not match service name:\n    `${EMBER_IMPORT} const controller = Controller.extend({ specialName: Ember.inject.service('service-name') });`,\n    `${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ specialName: service('service-name') });`,\n    `${RENAMED_SERVICE_IMPORT} class Test { @service(\"specialName\") serviceName }`,\n\n    // When usage is ignored because of additional arguments:\n    `${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service('serviceName', EXTRA_PROPERTY) });`,\n    `${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service('serviceName', EXTRA_PROPERTY) });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: inject('serviceName', EXTRA_PROPERTY) });`,\n\n    // When usage is ignored because of template literal:\n    `${EMBER_IMPORT} export default Component.extend({ serviceName: Ember.inject.service(\\`serviceName\\`) });`,\n    `${SERVICE_IMPORT} export default Component.extend({ serviceName: service(\\`serviceName\\`) });`,\n    `${RENAMED_SERVICE_IMPORT} class Test { @service(\\`specialName\\`) serviceName }`,\n\n    // Not Ember's `service()` function:\n    \"export default Component.extend({ serviceName: otherFunction('serviceName') });\",\n    `${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service.otherFunction('serviceName') });`,\n    \"export default Component.extend({ serviceName: inject.otherFunction('serviceName') });\",\n    'class Test { @otherDecorator(\"name\") name }',\n\n    'export default Component.extend({ ...foo });',\n  ],\n  invalid: [\n    // `Component` examples:\n    {\n      code: `${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service('serviceName') });`,\n      output: `${RENAMED_SERVICE_IMPORT} export default Component.extend({ serviceName: service() });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],\n    },\n    {\n      code: `${SERVICE_IMPORT} export default Component.extend({ serviceName: inject('serviceName') });`,\n      output: `${SERVICE_IMPORT} export default Component.extend({ serviceName: inject() });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],\n    },\n\n    // `Controller` examples:\n    {\n      code: `${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ serviceName: service('serviceName') });`,\n      output: `${RENAMED_SERVICE_IMPORT} const controller = Controller.extend({ serviceName: service() });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],\n    },\n    {\n      code: `${SERVICE_IMPORT} const controller = Controller.extend({ serviceName: inject('serviceName') });`,\n      output: `${SERVICE_IMPORT} const controller = Controller.extend({ serviceName: inject() });`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],\n    },\n\n    // Decorator:\n    {\n      code: `${RENAMED_SERVICE_IMPORT} class Test { @service(\"serviceName\") serviceName }`,\n      output: `${RENAMED_SERVICE_IMPORT} class Test { @service() serviceName }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],\n    },\n    // Decorator, with new import\n    {\n      code: `${NEW_SERVICE_IMPORT} class Test { @service(\"serviceName\") serviceName }`,\n      output: `${NEW_SERVICE_IMPORT} class Test { @service() serviceName }`,\n      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-unused-services.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-unused-services');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\nconst SERVICE_NAME = 'fooName';\nconst SERVICE_IMPORT = \"import {inject as service} from '@ember/service';\";\nconst NEW_SERVICE_IMPORT = \"import {service} from '@ember/service';\";\nconst EO_IMPORTS = \"import {computed, get, getProperties, observer} from '@ember/object';\";\nconst RENAMED_EO_IMPORTS =\n  \"import {computed as cp, get as g, getProperties as gp, observer as ob} from '@ember/object';\";\nconst ALIAS_IMPORT = \"import {alias} from '@ember/object/computed';\";\nconst RENAMED_ALIAS_IMPORT = \"import {alias as al} from '@ember/object/computed';\";\nconst OBSERVES_IMPORT = \"import {observes} from '@ember-decorators/object';\";\nconst RENAMED_OBSERVES_IMPORT = \"import {observes as obs} from '@ember-decorators/object';\";\nconst EMBER_IMPORT = \"import Ember from 'ember';\";\nconst RENAMED_EMBER_IMPORT = \"import Em from 'ember';\";\n\n/**\n * Generate an array of usecases using the given property name\n * @param {String} propertyName The given property name to access\n * @returns {Array}\n */\nfunction generateUseCasesFor(propertyName) {\n  return [\n    `this.${propertyName};`,\n    `this.${propertyName}[0];`,\n    `this.${propertyName}.prop;`,\n    `this.${propertyName}.func();`,\n    `this.get('${propertyName}');`,\n    `this.get('${propertyName}.prop');`,\n    `this.getProperties('a', '${propertyName}');`,\n    `this.getProperties('a', '${propertyName}.prop');`,\n    `this.getProperties(['a', '${propertyName}']);`,\n    `this.getProperties(['a', '${propertyName}.prop']);`,\n    `const { a, b, ${propertyName} } = this;`,\n    `let { c, ${propertyName} : prop, d } = this;`,\n  ];\n}\n\n/**\n * Generate an array of usecases with Ember.get and Ember.getProperties using the given property name\n * @param {String} propertyName The given property name to access\n * @param {Boolean} renamed Whether or not the imports are renamed\n * @returns {Array}\n */\nfunction generateEmberObjectUseCasesFor(propertyName, renamed = false) {\n  const getName = renamed ? 'g' : 'get';\n  const getPropertiesName = renamed ? 'gp' : 'getProperties';\n  return [\n    `${getName}(this, '${propertyName}');`,\n    `${getName}(this, '${propertyName}.prop');`,\n    `${getPropertiesName}(this, 'a', '${propertyName}');`,\n    `${getPropertiesName}(this, 'a', '${propertyName}.prop');`,\n    `${getPropertiesName}(this, ['a', '${propertyName}']);`,\n    `${getPropertiesName}(this, ['a', '${propertyName}.prop']);`,\n  ];\n}\n\n/**\n * Generate an array of usecases with a computed macro `alias` using the given property name\n * @param {String} propertyName The given property name to access\n * @param {Boolean} renamed Whether or not the imports are renamed\n * @returns {Array}\n */\nfunction generateMacroUseCasesFor(propertyName, renamed = false) {\n  const aliasName = renamed ? 'al' : 'alias';\n  const aliasImport = renamed ? RENAMED_ALIAS_IMPORT : ALIAS_IMPORT;\n  return [\n    `${SERVICE_IMPORT}${aliasImport} class MyClass { @service() ${propertyName}; @${aliasName}('${propertyName}.prop') someAlias; }`,\n    `${SERVICE_IMPORT}${aliasImport} Component.extend({ ${SERVICE_NAME}: service(), someAlias: ${aliasName}('${propertyName}.prop') });`,\n  ];\n}\n\n/**\n * Generate an array of usecases with a computed property using the given property name\n * @param {String} propertyName The given property name to access\n * @param {Boolean} renamed Whether or not the imports are renamed\n * @returns {Array}\n */\nfunction generateComputedUseCasesFor(propertyName, renamed = false) {\n  const computedName = renamed ? 'cp' : 'computed';\n  const computedImport = renamed ? RENAMED_EO_IMPORTS : EO_IMPORTS;\n  const emberName = renamed ? 'Em' : 'Ember';\n  const emberImport = renamed ? RENAMED_EMBER_IMPORT : EMBER_IMPORT;\n  return [\n    `${SERVICE_IMPORT}${computedImport} class MyClass { @service() ${propertyName}; @${computedName}('${propertyName}.prop') get someComputed() {} }`,\n    `${SERVICE_IMPORT}${computedImport} Component.extend({ ${SERVICE_NAME}: service(), someComputed: ${computedName}('${propertyName}.prop', ()=>{}) });`,\n    `${SERVICE_IMPORT}${computedImport} Component.extend({ ${SERVICE_NAME}: service(), someComputed: ${computedName}.alias('${propertyName}.prop', ()=>{}) });`,\n    `${SERVICE_IMPORT}${emberImport} Component.extend({ ${SERVICE_NAME}: service(), someComputed: ${emberName}.computed('${propertyName}.prop', ()=>{}) });`,\n    `${SERVICE_IMPORT}${emberImport} Component.extend({ ${SERVICE_NAME}: service(), someComputed: ${emberName}.computed.alias('${propertyName}.prop', ()=>{}) });`,\n  ];\n}\n\n/**\n * Generate an array of usecases with an observer using the given property name\n * @param {String} propertyName The given property name to access\n * @param {Boolean} renamed Whether or not the imports are renamed\n * @returns {Array}\n */\nfunction generateObserverUseCasesFor(propertyName, renamed = false) {\n  const observerName = renamed ? 'ob' : 'observer';\n  const observerImport = renamed ? RENAMED_EO_IMPORTS : EO_IMPORTS;\n  const observesName = renamed ? 'obs' : 'observes';\n  const observesImport = renamed ? RENAMED_OBSERVES_IMPORT : OBSERVES_IMPORT;\n  const emberName = renamed ? 'Em' : 'Ember';\n  const emberImport = renamed ? RENAMED_EMBER_IMPORT : EMBER_IMPORT;\n  return [\n    `${SERVICE_IMPORT}${observerImport} Component.extend({ ${SERVICE_NAME}: service(), someObserved: ${observerName}('${propertyName}.prop', ()=>{}) });`,\n    `${SERVICE_IMPORT}${observerImport} Component.extend({ ${SERVICE_NAME}: service(), someObserved: on('init', ${observerName}('${propertyName}.prop', ()=>{})) });`,\n    `${SERVICE_IMPORT}${emberImport} Component.extend({ ${SERVICE_NAME}: service(), someObserved: ${emberName}.observer('${propertyName}.prop', ()=>{}) });`,\n    `${SERVICE_IMPORT}${emberImport} Component.extend({ ${SERVICE_NAME}: service(), someObserved: on('init', ${emberName}.observer('${propertyName}.prop', ()=>{})) });`,\n    `${SERVICE_IMPORT}${observesImport} class MyClass { @service() ${propertyName}; @${observesName}('${propertyName}.prop') get someVal() {} }`,\n  ];\n}\n\n/**\n * Generate an array of valid test cases\n * @returns {Array}\n */\nfunction generateValid(importString = SERVICE_IMPORT) {\n  const valid = [];\n\n  const useCases = generateUseCasesFor(SERVICE_NAME);\n  for (const use of useCases) {\n    valid.push(\n      `${importString} class MyClass { @service('foo') ${SERVICE_NAME}; fooFunc() {${use}} }`,\n      `${importString} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${use}} }`,\n      `${importString} class MyClass { @service() '${SERVICE_NAME}'; fooFunc() {${use}} }`,\n      `${importString} Component.extend({ ${SERVICE_NAME}: service('foo'), fooFunc() {${use}} });`,\n      `${importString} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${use}} });`,\n      `${importString} Component.extend({ '${SERVICE_NAME}': service(), fooFunc() {${use}} });`\n    );\n  }\n\n  const emberObjectUseCases = [\n    generateEmberObjectUseCasesFor(SERVICE_NAME),\n    generateEmberObjectUseCasesFor(SERVICE_NAME, true),\n  ];\n  for (const [idx, useCases] of emberObjectUseCases.entries()) {\n    const imports = idx === 0 ? EO_IMPORTS : RENAMED_EO_IMPORTS;\n    for (const use of useCases) {\n      valid.push(\n        `${importString}${imports} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${use}} }`,\n        `${importString}${imports} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${use}} });`\n      );\n    }\n  }\n\n  return valid;\n}\n\n// Testing for unrelated props + some edge cases\nconst unrelatedPropUses = generateUseCasesFor('unrelatedProp');\nconst edgeCases = ['let foo;', `this.prop.${SERVICE_NAME};`];\nconst nonUses = [...unrelatedPropUses, ...edgeCases].join('');\nconst emberObjectUses1 = generateEmberObjectUseCasesFor(SERVICE_NAME).join('');\nconst emberObjectUses2 = generateEmberObjectUseCasesFor('unrelatedProp').join('');\n\nruleTester.run('no-unused-services', rule, {\n  valid: [\n    ...generateValid(),\n    ...generateValid(NEW_SERVICE_IMPORT),\n    ...generateMacroUseCasesFor(SERVICE_NAME),\n    ...generateMacroUseCasesFor(SERVICE_NAME, true),\n    ...generateComputedUseCasesFor(SERVICE_NAME),\n    ...generateComputedUseCasesFor(SERVICE_NAME, true),\n    ...generateObserverUseCasesFor(SERVICE_NAME),\n    ...generateObserverUseCasesFor(SERVICE_NAME, true),\n    `class MyClass { @service() ${SERVICE_NAME}; }`,\n    `Component.extend({ ${SERVICE_NAME}: service() });`,\n    `Component.extend({ ${SERVICE_NAME}: Ember.inject.service() });`,\n    `${SERVICE_IMPORT} class MyClass {}`,\n    `${SERVICE_IMPORT} Component.extend({});`,\n    'const foo = service();',\n    \"import ComputedProperty from '@ember/object/computed';\",\n  ],\n  invalid: [\n    {\n      code: `${SERVICE_IMPORT} class MyClass { @service('foo') ${SERVICE_NAME}; fooFunc() {${nonUses}} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass {  fooFunc() {${nonUses}} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    // With new import\n    {\n      code: `${NEW_SERVICE_IMPORT} class MyClass { @service('foo') ${SERVICE_NAME}; fooFunc() {${nonUses}} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${NEW_SERVICE_IMPORT} class MyClass {  fooFunc() {${nonUses}} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${nonUses}} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass {  fooFunc() {${nonUses}} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service('foo'), fooFunc() {${nonUses}} });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} Component.extend({  fooFunc() {${nonUses}} });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service('foo') });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} Component.extend({  });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${nonUses}} });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} Component.extend({  fooFunc() {${nonUses}} });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service() });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} Component.extend({  });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    /* Using get/getProperties without @ember/object import */\n    {\n      code: `${SERVICE_IMPORT} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${emberObjectUses1}} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass {  fooFunc() {${emberObjectUses1}} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${emberObjectUses1}} });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} Component.extend({  fooFunc() {${emberObjectUses1}} });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    /* Using get/getProperties with @ember/object import for an unrelatedProp */\n    {\n      code: `${SERVICE_IMPORT}${EO_IMPORTS} class MyClass { @service() ${SERVICE_NAME}; fooFunc() {${emberObjectUses2}} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EO_IMPORTS} class MyClass {  fooFunc() {${emberObjectUses2}} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT}${EO_IMPORTS} Component.extend({ ${SERVICE_NAME}: service(), fooFunc() {${emberObjectUses2}} });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EO_IMPORTS} Component.extend({  fooFunc() {${emberObjectUses2}} });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    /* Using computed props and macros without the imports */\n    {\n      code: `${SERVICE_IMPORT} class MyClass { @service() ${SERVICE_NAME}; @alias('${SERVICE_NAME}') someAlias; @computed('${SERVICE_NAME}.prop') get someComputed() {} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass {  @alias('${SERVICE_NAME}') someAlias; @computed('${SERVICE_NAME}.prop') get someComputed() {} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), someAlias1: alias('${SERVICE_NAME}'), someAlias2: computed.alias('${SERVICE_NAME}.prop'), someComputed: computed('${SERVICE_NAME}.prop', ()=>{}) });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} Component.extend({  someAlias1: alias('${SERVICE_NAME}'), someAlias2: computed.alias('${SERVICE_NAME}.prop'), someComputed: computed('${SERVICE_NAME}.prop', ()=>{}) });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    /* Using computed props and macros with the imports for an unrelatedProp */\n    {\n      code: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} class MyClass { @service() ${SERVICE_NAME}; @alias('unrelatedProp', '${SERVICE_NAME}') someAlias; @computed('unrelatedProp.prop') get someComputed() {} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} class MyClass {  @alias('unrelatedProp', '${SERVICE_NAME}') someAlias; @computed('unrelatedProp.prop') get someComputed() {} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), someAlias1: alias('unrelatedProp', '${SERVICE_NAME}'), someAlias2: computed.alias('unrelatedProp.prop'), someComputed: computed('unrelatedProp.prop', ()=>{}) });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} Component.extend({  someAlias1: alias('unrelatedProp', '${SERVICE_NAME}'), someAlias2: computed.alias('unrelatedProp.prop'), someComputed: computed('unrelatedProp.prop', ()=>{}) });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} class MyClass { @service() ${SERVICE_NAME}; @alias(${SERVICE_NAME}) someAlias; @computed(${SERVICE_NAME}) get someComputed() {} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} class MyClass {  @alias(${SERVICE_NAME}) someAlias; @computed(${SERVICE_NAME}) get someComputed() {} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), someAlias1: alias(${SERVICE_NAME}), someAlias2: computed.alias(${SERVICE_NAME}), someComputed: computed(${SERVICE_NAME}, ()=>{}) });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EO_IMPORTS}${ALIAS_IMPORT} Component.extend({  someAlias1: alias(${SERVICE_NAME}), someAlias2: computed.alias(${SERVICE_NAME}), someComputed: computed(${SERVICE_NAME}, ()=>{}) });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    /* Using dummy macros that don't have dependent key props */\n    {\n      code: `${SERVICE_IMPORT} import {foobar} from '@ember/object/computed'; class MyClass { @service() ${SERVICE_NAME}; @foobar('${SERVICE_NAME}') someFoobar; }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} import {foobar} from '@ember/object/computed'; class MyClass {  @foobar('${SERVICE_NAME}') someFoobar; }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} import {foobar} from '@ember/object/computed'; Component.extend({ ${SERVICE_NAME}: service(), someFoobar: foobar('${SERVICE_NAME}') });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} import {foobar} from '@ember/object/computed'; Component.extend({  someFoobar: foobar('${SERVICE_NAME}') });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT}${EMBER_IMPORT} Component.extend({ ${SERVICE_NAME}: service(), someFoobar: Ember.computed.foobar('${SERVICE_NAME}') });`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT}${EMBER_IMPORT} Component.extend({  someFoobar: Ember.computed.foobar('${SERVICE_NAME}') });`,\n            },\n          ],\n          type: 'Property',\n        },\n      ],\n    },\n    /* Multiple classes */\n    {\n      code: `${SERVICE_IMPORT} class MyClass1 { @service() ${SERVICE_NAME}; } class MyClass2 { fooFunc() {this.${SERVICE_NAME};} }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass1 {  } class MyClass2 { fooFunc() {this.${SERVICE_NAME};} }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} class MyClass1 { fooFunc() {this.${SERVICE_NAME};} } class MyClass2 { @service() ${SERVICE_NAME}; }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass1 { fooFunc() {this.${SERVICE_NAME};} } class MyClass2 {  }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    /* Nested classes */\n    {\n      code: `${SERVICE_IMPORT} class MyClass1 { @service() ${SERVICE_NAME}; fooFunc1() { class MyClass2 { fooFunc2() {this.${SERVICE_NAME};} } } }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass1 {  fooFunc1() { class MyClass2 { fooFunc2() {this.${SERVICE_NAME};} } } }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `${SERVICE_IMPORT} class MyClass1 { fooFunc1() {this.${SERVICE_NAME};} fooFunc2() { class MyClass2 { @service() ${SERVICE_NAME}; } } }`,\n      output: null,\n      errors: [\n        {\n          messageId: 'main',\n          suggestions: [\n            {\n              messageId: 'removeServiceInjection',\n              output: `${SERVICE_IMPORT} class MyClass1 { fooFunc1() {this.${SERVICE_NAME};} fooFunc2() { class MyClass2 {  } } }`,\n            },\n          ],\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/no-volatile-computed-properties.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/no-volatile-computed-properties');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022, sourceType: 'module' } });\nruleTester.run('no-volatile-computed-properties', rule, {\n  valid: [\n    'computed()',\n    \"computed('prop', function() { return this.prop; })\",\n    'computed().other()',\n    'volatile()',\n    'other().volatile()',\n    'volatile().computed()',\n\n    {\n      // Decorator:\n      code: \"class Test { @computed('prop') get someProp() {} }\",\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n  ].map(addComputedImport),\n  invalid: [\n    {\n      code: 'computed().volatile()',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n\n    {\n      code: \"import Ember from 'ember'; Ember.computed().volatile()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n\n    {\n      code: \"computed('prop', function() { return this.prop; }).volatile()\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n    },\n\n    /*\n    // This is invalid syntax according to @babel/eslint-parser.\n    {\n      // Decorator:\n      code: \"class Test { @computed('prop').volatile() get someProp() {} }\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'Identifier' }],\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n    */\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/order-in-components.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/order-in-components');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\neslintTester.run('order-in-components', rule, {\n  valid: [\n    'export default Component.extend();',\n    'export default Component.extend({ ...foo });',\n    `export default Component.extend({\n      ...foo,\n      role: \"sloth\",\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {},\n\n      });`,\n    `export default Component.extend({\n        role: ${`${'sloth'}`},\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n    `export default Component.extend({\n        role: \"sloth\",\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n    `export default Component.extend({\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n    `export default Component.extend(TestMixin, {\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n    `export default Component.extend(TestMixin, TestMixin2, {\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n    `\n      import Ember from 'ember';\n      import {inject as service} from '@ember/service';\n      export default Component.extend({\n        abc: Ember.inject.service(),\n        def: service(),\n\n        role: \"sloth\",\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n    `\n        import { inject } from '@ember/service';\n        export default Component.extend({\n          abc: inject(),\n\n          role: \"sloth\",\n\n          levelOfHappiness: computed(\"attitude\", \"health\", () => {\n          })\n        });\n      `,\n    `export default Component.extend({\n        role: \"sloth\",\n        abc: [],\n        def: {},\n\n        ghi: alias(\"def\")\n      });`,\n    `import {observer} from '@ember/object';\n    export default Component.extend({\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        abc: Ember.observer(\"aaaa\", () => {\n        }),\n\n        def: observer(\"aaaa\", () => {\n        }),\n\n        actions: {}\n      });`,\n    `import {observer} from '@ember/object';\n    export default Component.extend({\n        abc: observer(\"aaaa\", () => {\n        }),\n\n        init() {\n        },\n\n        actions: {},\n\n        customFunc() {\n          return true;\n        }\n      });`,\n    `\n      import {inject as service} from '@ember/service';\n      import {observer} from '@ember/object';\n      export default Component.extend({\n        igh: service(),\n\n        abc: [],\n        def: true,\n\n        singleComp: alias(\"abc\"),\n\n        multiComp: computed(() => {\n        }),\n\n        obs: observer(\"aaa\", () => {\n        }),\n\n        init() {\n        },\n\n        actions: {},\n\n        customFunc() {\n          return true;\n        }\n      });`,\n    `export default Component.extend({\n        init() {\n        },\n        didReceiveAttrs() {\n        },\n        willRender() {\n        },\n        willInsertElement() {\n        },\n        didInsertElement() {\n        },\n        didRender() {\n        },\n        didUpdateAttrs() {\n        },\n        willUpdate() {\n        },\n        didUpdate() {\n        },\n        willDestroyElement() {\n        },\n        willClearRender() {\n        },\n        didDestroyElement() {\n        },\n\n        actions: {}\n      });`,\n    `\n      import {inject as service} from '@ember/service';\n      export default Component.extend({\n        test: service(),\n\n        didReceiveAttrs() {\n        },\n\n        tSomeAction: task(function* (url) {\n        })\n      });\n    `,\n    `\n      import {inject as service} from '@ember/service';\n      export default Component.extend({\n        test: service(),\n\n        test2: computed.equal(\"asd\", \"qwe\"),\n\n        didReceiveAttrs() {\n        },\n\n        tSomeAction: task(function* (url) {\n        }).restartable()\n      });\n    `,\n    `\n      import {inject as service} from '@ember/service';\n      export default Component.extend({\n        test: service(),\n\n        someEmptyMethod() {},\n\n        didReceiveAttrs() {\n        },\n\n        tSomeAction: task(function* (url) {\n        }),\n\n        _anotherPrivateFnc() {\n          return true;\n        }\n      });\n    `,\n    `export default Component.extend({\n        classNameBindings: [\"filterDateSelectClass\"],\n        content: [],\n        currentMonthEndDate: null,\n        currentMonthStartDate: null,\n        optionValuePath: \"value\",\n        optionLabelPath: \"label\",\n        typeOfDate: null,\n        action: K\n      });`,\n    `export default Component.extend({\n        role: \"sloth\",\n\n        levelOfHappiness: computed.or(\"asd\", \"qwe\"),\n\n        actions: {}\n      });`,\n    `export default Component.extend({\n        role: \"sloth\",\n\n        levelOfHappiness: computed(function() {}),\n\n        actions: {}\n      });`,\n    `export default Component.extend({\n        role: \"sloth\",\n\n        levelOfHappiness: computed(function() {\n        }),\n\n        actions: {}\n      });`,\n    {\n      code: `\n        import Ember from 'ember';\n        export default Component.extend({\n          role: \"sloth\",\n\n          computed1: computed(function() {\n          }),\n          computed2: alias('computed1'),\n\n          actions: {},\n\n          foobar: Ember.inject.service(),\n        });\n      `,\n      options: [\n        {\n          order: ['property', 'multi-line-function', 'single-line-function', 'actions'],\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        export default Component.extend({\n          role: \"sloth\",\n\n          computed1: alias('computed2'),\n          computed2: computed(function() {\n          }),\n          computed3: alias('computed1'),\n\n          actions: {},\n\n          foobar: Ember.inject.service(),\n        });\n      `,\n      options: [\n        {\n          order: ['property', ['single-line-function', 'multi-line-function'], 'actions'],\n        },\n      ],\n    },\n    `export default Component.extend({\n        role: \"sloth\",\n        qwe: foo ? 'bar' : null,\n        abc: [],\n        def: {},\n\n        ghi: alias(\"def\")\n      });`,\n    `export default Component.extend({\n        template: hbs\\`Hello world {{name}}\\`,\n        name: \"Jon Snow\",\n        actions: {}\n      });`,\n    `export default Component.extend({\n        layout,\n        tabindex: -1,\n\n        someComputedValue: computed.reads('count'),\n      });`,\n    `export default Component.extend({\n        foo: computed(function() {\n        }).volatile(),\n        bar: computed(function() {\n        })\n      });`,\n    `export default Component.extend({\n        onFoo() {},\n        onFoo: () => {},\n        foo: computed(function() {\n        }).volatile(),\n        bar() { const foo = 'bar'}\n      });`,\n    {\n      code: `export default Component.extend({\n        onFoo() {},\n        onFoo: () => {},\n        foo: computed(function() {\n        }).volatile(),\n        bar() { const foo = 'bar'}\n      });`,\n      options: [\n        {\n          order: [\n            'property',\n            'empty-method',\n            'single-line-function',\n            'multi-line-function',\n            'method',\n          ],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: `export default Component.extend({\n        prop: null,\n        actions: {\n          action: () => {}\n        },\n        customProp: { a: 1 }\n      });`,\n      options: [\n        {\n          order: ['property', 'actions', 'custom:customProp', 'method'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n  ],\n  invalid: [\n    {\n      code: `export default Component.extend({\n        actions: {},\n\n        role: \"sloth\",\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      output: `export default Component.extend({\n        role: \"sloth\",\n\n        actions: {},\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      errors: [\n        {\n          message: 'The \"role\" property should be above the actions hash on line 2',\n          line: 4,\n        },\n        {\n          message: 'The \"vehicle\" single-line function should be above the actions hash on line 2',\n          line: 6,\n        },\n        {\n          message:\n            'The \"levelOfHappiness\" multi-line function should be above the actions hash on line 2',\n          line: 8,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        actions: {},\n\n        role: ${`${'sloth'}`},\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      output: `export default Component.extend({\n        role: ${`${'sloth'}`},\n\n        actions: {},\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      errors: [\n        {\n          message: 'The \"role\" property should be above the actions hash on line 2',\n          line: 4,\n        },\n        {\n          message: 'The \"vehicle\" single-line function should be above the actions hash on line 2',\n          line: 6,\n        },\n        {\n          message:\n            'The \"levelOfHappiness\" multi-line function should be above the actions hash on line 2',\n          line: 8,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        vehicle: alias(\"car\"),\n\n        role: \"sloth\",\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n      output: `export default Component.extend({\n        role: \"sloth\",\n\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"role\" property should be above the \"vehicle\" single-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        vehicle: alias(\"car\"),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      output: `export default Component.extend({\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 5,\n        },\n        {\n          message:\n            'The \"role\" property should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend(TestMixin, {\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        vehicle: alias(\"car\"),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      output: `export default Component.extend(TestMixin, {\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 5,\n        },\n        {\n          message:\n            'The \"role\" property should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend(TestMixin, TestMixin2, {\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        vehicle: alias(\"car\"),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      output: `export default Component.extend(TestMixin, TestMixin2, {\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 5,\n        },\n        {\n          message:\n            'The \"role\" property should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Component.extend({\n        abc: true,\n        i18n: service()\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Component.extend({\n        i18n: service(),\n              abc: true,\n});`,\n      errors: [\n        {\n          message: 'The \"i18n\" service injection should be above the \"abc\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Component.extend({\n        vehicle: alias(\"car\"),\n        i18n: service()\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Component.extend({\n        i18n: service(),\n              vehicle: alias(\"car\"),\n});`,\n      errors: [\n        {\n          message:\n            'The \"i18n\" service injection should be above the \"vehicle\" single-line function on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `\n        import { inject } from '@ember/service';\n        export default Component.extend({\n          vehicle: alias(\"car\"),\n          i18n: inject()\n        });\n      `,\n      output: `\n        import { inject } from '@ember/service';\n        export default Component.extend({\n          i18n: inject(),\n                  vehicle: alias(\"car\"),\n});\n      `,\n      errors: [\n        {\n          message:\n            'The \"i18n\" service injection should be above the \"vehicle\" single-line function on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import {observer} from '@ember/object';\n      export default Component.extend({\n        levelOfHappiness: observer(\"attitude\", \"health\", () => {\n        }),\n        vehicle: alias(\"car\")\n      });`,\n      output: `import {observer} from '@ember/object';\n      export default Component.extend({\n        vehicle: alias(\"car\"),\n              levelOfHappiness: observer(\"attitude\", \"health\", () => {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"levelOfHappiness\" observer on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import {observer} from '@ember/object';\n      export default Component.extend({\n        levelOfHappiness: observer(\"attitude\", \"health\", () => {\n        }),\n        aaa: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      output: `import {observer} from '@ember/object';\n      export default Component.extend({\n        aaa: computed(\"attitude\", \"health\", () => {\n        }),\n              levelOfHappiness: observer(\"attitude\", \"health\", () => {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"aaa\" multi-line function should be above the \"levelOfHappiness\" observer on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import {observer} from '@ember/object';\n      export default Component.extend({\n        init() {\n        },\n        levelOfHappiness: observer(\"attitude\", \"health\", () => {\n        })\n      });`,\n      output: `import {observer} from '@ember/object';\n      export default Component.extend({\n        levelOfHappiness: observer(\"attitude\", \"health\", () => {\n        }),\n              init() {\n        },\n});`,\n      errors: [\n        {\n          message:\n            'The \"levelOfHappiness\" observer should be above the \"init\" lifecycle hook on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        actions: {},\n        init() {\n        }\n      });`,\n      output: `export default Component.extend({\n        init() {\n        },\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"init\" lifecycle hook should be above the actions hash on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        customFunc() {\n          const foo = 'bar';\n        },\n        actions: {}\n      });`,\n      output: `export default Component.extend({\n        actions: {},\n              customFunc() {\n          const foo = 'bar';\n        },\n});`,\n      errors: [\n        {\n          message: 'The actions hash should be above the \"customFunc\" method on line 2',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        tAction: test(function() {\n        }),\n        actions: {}\n      });`,\n      output: `export default Component.extend({\n        actions: {},\n              tAction: test(function() {\n        }),\n});`,\n      errors: [\n        {\n          message: 'The actions hash should be above the \"tAction\" method on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend(TestMixin, TestMixin2, {\n        foo: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        vehicle: alias(\"car\"),\n\n        role: \"sloth\",\n\n        actions: {}\n      });`,\n      output: `export default Component.extend(TestMixin, TestMixin2, {\n        role: \"sloth\",\n\n        foo: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        vehicle: alias(\"car\"),\n\n        actions: {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"levelOfHappiness\" multi-line function on line 4',\n          line: 7,\n        },\n        {\n          message: 'The \"role\" property should be above the \"foo\" single-line function on line 2',\n          line: 9,\n        },\n      ],\n    },\n    {\n      code: `let foo = 'foo';\n\n      export default Component.extend(TestMixin, TestMixin2, {\n        actions: {},\n        [foo]: 'foo',\n      });`,\n      output: `let foo = 'foo';\n\n      export default Component.extend(TestMixin, TestMixin2, {\n        [foo]: 'foo',\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The property should be above the actions hash on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/components/some-component/component.js',\n      code: `export default CustomComponent.extend({\n        actions: {},\n        role: \"sloth\",\n      });`,\n      output: `export default CustomComponent.extend({\n        role: \"sloth\",\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"role\" property should be above the actions hash on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/components/some-component.js',\n      code: `export default CustomComponent.extend({\n        actions: {},\n        role: \"sloth\",\n      });`,\n      output: `export default CustomComponent.extend({\n        role: \"sloth\",\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"role\" property should be above the actions hash on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/twisted-path/some-component.js',\n      code: `export default Component.extend({\n        actions: {},\n        role: \"sloth\",\n      });`,\n      output: `export default Component.extend({\n        role: \"sloth\",\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"role\" property should be above the actions hash on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        name: \"Jon Snow\",\n        actions: {},\n        template: hbs\\`Hello world {{name}}\\`,\n      });`,\n      output: `export default Component.extend({\n        name: \"Jon Snow\",\n        template: hbs\\`Hello world {{name}}\\`,\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"template\" property should be above the actions hash on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        layout,\n        someComputedValue: computed.reads('count'),\n\n        tabindex: -1,\n      });`,\n      output: `export default Component.extend({\n        layout,\n        tabindex: -1,\n              someComputedValue: computed.reads('count'),\n\n});`,\n      errors: [\n        {\n          message:\n            'The \"tabindex\" property should be above the \"someComputedValue\" single-line function on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        foo: computed(function() {\n        }).volatile(),\n        name: \"Jon Snow\",\n      });`,\n      output: `export default Component.extend({\n        name: \"Jon Snow\",\n              foo: computed(function() {\n        }).volatile(),\n});`,\n      errors: [\n        {\n          message: 'The \"name\" property should be above the \"foo\" multi-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        actions: {},\n        didReceiveAttrs() {},\n        willDestroyElement() {},\n        didInsertElement() {},\n        init() {},\n      });`,\n      output: `export default Component.extend({\n        didReceiveAttrs() {},\n        actions: {},\n        willDestroyElement() {},\n        didInsertElement() {},\n        init() {},\n      });`,\n      errors: [\n        {\n          message:\n            'The \"didReceiveAttrs\" lifecycle hook should be above the actions hash on line 2',\n          line: 3,\n        },\n        {\n          message:\n            'The \"willDestroyElement\" lifecycle hook should be above the actions hash on line 2',\n          line: 4,\n        },\n        {\n          message:\n            'The \"didInsertElement\" lifecycle hook should be above the actions hash on line 2',\n          line: 5,\n        },\n        {\n          message: 'The \"init\" lifecycle hook should be above the actions hash on line 2',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        foo: computed(function() {\n        }).volatile(),\n        onFoo() {},\n        bar() { const foo = 'bar'},\n        onBar: () => {}\n      });`,\n      output: `export default Component.extend({\n        onFoo() {},\n        foo: computed(function() {\n        }).volatile(),\n        bar() { const foo = 'bar'},\n        onBar: () => {}\n      });`,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            'The \"onFoo\" empty method should be above the \"foo\" multi-line function on line 2',\n          line: 4,\n        },\n        {\n          message:\n            'The \"onBar\" empty method should be above the \"foo\" multi-line function on line 2',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        vehicle: alias(\"car\"),\n\n        actions: {}\n      });`,\n      output: `export default Component.extend({\n        vehicle: alias(\"car\"),\n\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n\n        actions: {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"levelOfHappiness\" multi-line function on line 2',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        prop: null,\n        actions: {},\n        customProp: { a: 1 }\n      });`,\n      output: `export default Component.extend({\n        prop: null,\n        customProp: { a: 1 },\n              actions: {},\n});`,\n      options: [\n        {\n          order: ['property', 'custom:customProp', 'actions', 'method'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message: 'The \"customProp\" custom property should be above the actions hash on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        customProp: { a: 1 },\n        aMethod() {\n          console.log('not empty');\n        }\n      });`,\n      output: `export default Component.extend({\n        aMethod() {\n          console.log('not empty');\n        },\n              customProp: { a: 1 },\n});`,\n      options: [\n        {\n          order: ['method', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            'The \"aMethod\" method should be above the \"customProp\" custom property on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default Component.extend({\n        role: \"sloth\",\n        ...spread,\n      });`,\n      output: `export default Component.extend({\n        ...spread,\n              role: \"sloth\",\n});`,\n      errors: [\n        {\n          message: 'The spread property should be above the \"role\" property on line 2',\n          line: 3,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/order-in-controllers.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/order-in-controllers');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\neslintTester.run('order-in-controllers', rule, {\n  valid: [\n    'export default Controller.extend();',\n    'export default Controller.extend({ ...foo });',\n    `\n      import {inject as service} from '@ember/service';\n      import {inject as controller} from '@ember/controller';\n      export default Controller.extend({\n        application: controller(),\n        currentUser: service(),\n        queryParams: [],\n        customProp: \"test\",\n        actions: {},\n        _customAction() { const foo = 'bar'; },\n        _customAction2: function() { const foo = 'bar'; },\n        tSomeTask: task(function* () {})\n      });`,\n    `\n      import {inject} from '@ember/service';\n      export default Controller.extend({\n        currentUser: inject(),\n        queryParams: [],\n        customProp: \"test\",\n        actions: {},\n        _customAction() {},\n        _customAction2: function() {},\n        tSomeTask: task(function* () {})\n      });`,\n    `import {observer} from '@ember/object';\n    export default Controller.extend({\n        queryParams: [],\n        customProp: \"test\",\n        comp: computed(\"test\", function() {}),\n        obs: observer(\"asd\", function() {}),\n        actions: {}\n      });`,\n    `export default Controller.extend({\n        customProp: \"test\",\n        comp: computed(\"test\", function() {}),\n        comp2: computed(\"test\", function() {\n        }),\n        actions: {},\n        _customAction() { const foo = 'bar'; }\n      });`,\n    {\n      code: `export default Controller.extend({\n        actions: {},\n        comp: computed(\"test\", function() {}),\n        customProp: \"test\",\n        comp2: computed(\"test\", function() {\n        }),\n        _customAction() { const foo = 'bar'; }\n      });`,\n      options: [\n        {\n          order: ['actions', 'single-line-function'],\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          queryParams: [],\n          currentUser: service(),\n        });\n      `,\n      options: [\n        {\n          order: ['query-params', 'service'],\n        },\n      ],\n    },\n    {\n      code: `import {inject as controller} from '@ember/controller';\n      export default Controller.extend({\n        queryParams: [],\n        application: controller(),\n      });`,\n      options: [\n        {\n          order: ['query-params', 'controller'],\n        },\n      ],\n    },\n    `\n      import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        foo: service(),\n        someProp: null,\n        init() {\n          this._super(...arguments);\n        },\n        actions: {\n          onKeyPress: function (event) {}\n        }\n      });\n    `,\n    `\n      import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        foo: service(),\n        init() {\n          this._super(...arguments);\n        },\n        customFoo() {}\n      });\n    `,\n    `\n      import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        foo: service(),\n        init() {\n          this._super(...arguments);\n        }\n      });\n    `,\n    {\n      code: `export default Controller.extend({\n        prop: null,\n        actions: {\n          action: () => {}\n        },\n        customProp: { a: 1 }\n      });`,\n      options: [\n        {\n          order: ['property', 'actions', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    `import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      export default class UserController extends Controller {\n        @service currentUser;\n        queryParams = [];\n        customProp = 'test';\n        actions() {}\n      }`,\n    {\n      code: `import Controller from '@ember/controller';\n        import { inject as service } from '@ember/service';\n        export default class UserController extends Controller {\n          queryParams = [];\n          @service currentUser;\n          actions() {}\n        }`,\n      options: [\n        {\n          order: ['query-params', 'service', 'single-line-function'],\n        },\n      ],\n    },\n    {\n      code: `import Controller from '@ember/controller';\n        import { inject as service } from '@ember/service';\n        export default class UserController extends Controller {\n          @service currentUser;\n          queryParams = [];\n          customProp = 'test';\n        }`,\n      options: [\n        {\n          order: [['service', 'query-params'], 'property'],\n        },\n      ],\n    },\n    // spacing/indentation is intentionally not validated by this rule;\n    // only member ordering should matter.\n    `import Controller from '@ember/controller';\n      import { inject as service } from '@ember/service';\n      export default class UserController extends Controller {\n            @service currentUser;\n        queryParams = [];\n                 customProp = 'test';\n      }`,\n  ],\n  invalid: [\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        queryParams: [],\n        currentUser: service()\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        currentUser: service(),\n              queryParams: [],\n});`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `import {inject} from '@ember/service';\n      export default Controller.extend({\n        queryParams: [],\n        currentUser: inject()\n      });`,\n      output: `import {inject} from '@ember/service';\n      export default Controller.extend({\n        currentUser: inject(),\n              queryParams: [],\n});`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        currentUser: service(),\n        customProp: \"test\",\n        queryParams: []\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        currentUser: service(),\n        queryParams: [],\n              customProp: \"test\",\n});`,\n      errors: [\n        {\n          message: 'The \"queryParams\" property should be above the \"customProp\" property on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Controller.extend({\n        queryParams: [],\n        actions: {},\n        customProp: \"test\"\n      });`,\n      output: `export default Controller.extend({\n        queryParams: [],\n        customProp: \"test\",\n              actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"customProp\" property should be above the actions hash on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Controller.extend({\n        queryParams: [],\n        _customAction() { const foo = 'bar'; },\n        actions: {}\n      });`,\n      output: `export default Controller.extend({\n        queryParams: [],\n        actions: {},\n              _customAction() { const foo = 'bar'; },\n});`,\n      errors: [\n        {\n          message: 'The actions hash should be above the \"_customAction\" method on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Controller.extend({\n        test: \"asd\",\n        queryParams: [],\n        actions: {}\n      });`,\n      output: `export default Controller.extend({\n        queryParams: [],\n        test: \"asd\",\n        actions: {}\n      });`,\n      errors: [\n        {\n          message: 'The \"queryParams\" property should be above the \"test\" property on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import {inject as controller} from '@ember/controller';\n      import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        currentUser: service(),\n        application: controller()\n      });`,\n      output: `import {inject as controller} from '@ember/controller';\n      import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        application: controller(),\n              currentUser: service(),\n});`,\n      errors: [\n        {\n          message:\n            'The \"application\" controller injection should be above the \"currentUser\" service injection on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import {observer} from '@ember/object';\n      export default Controller.extend({\n        test: \"asd\",\n        obs: observer(\"asd\", function() {}),\n        comp: computed(\"asd\", function() {}),\n        actions: {}\n      });`,\n      output: `import {observer} from '@ember/object';\n      export default Controller.extend({\n        test: \"asd\",\n        comp: computed(\"asd\", function() {}),\n        obs: observer(\"asd\", function() {}),\n        actions: {}\n      });`,\n      errors: [\n        {\n          message: 'The \"comp\" single-line function should be above the \"obs\" observer on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/controllers/some-controller.js',\n      code: `import {inject as service} from '@ember/service';\n      export default CustomController.extend({\n        queryParams: [],\n        currentUser: service()\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default CustomController.extend({\n        currentUser: service(),\n              queryParams: [],\n});`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/some-feature/controller.js',\n      code: `import {inject as service} from '@ember/service';\n      export default CustomController.extend({\n        queryParams: [],\n        currentUser: service()\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default CustomController.extend({\n        currentUser: service(),\n              queryParams: [],\n});`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/twisted-path/some-controller.js',\n      code: `import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        queryParams: [],\n        currentUser: service()\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Controller.extend({\n        currentUser: service(),\n              queryParams: [],\n});`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          foo: service(),\n          actions: {\n            onKeyPress: function (event) {}\n          },\n          init() {\n            this._super(...arguments);\n          }\n        });\n      `,\n      output: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          foo: service(),\n          init() {\n            this._super(...arguments);\n          },\n                  actions: {\n            onKeyPress: function (event) {}\n          },\n});\n      `,\n      errors: [\n        {\n          message: 'The \"init\" lifecycle hook should be above the actions hash on line 5',\n          line: 8,\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          foo: service(),\n          customFoo() {},\n          init() {\n            this._super(...arguments);\n          }\n        });\n      `,\n      output: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          foo: service(),\n          init() {\n            this._super(...arguments);\n          },\n                  customFoo() {},\n});\n      `,\n      errors: [\n        {\n          message:\n            'The \"init\" lifecycle hook should be above the \"customFoo\" empty method on line 5',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          init() {\n            this._super(...arguments);\n          },\n          foo: service()\n        });\n      `,\n      output: `\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          foo: service(),\n                  init() {\n            this._super(...arguments);\n          },\n});\n      `,\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            'The \"foo\" service injection should be above the \"init\" lifecycle hook on line 4',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code: `\n        export default Controller.extend({\n          init() {\n            this._super(...arguments);\n          },\n          someProp: null\n        });\n      `,\n      output: `\n        export default Controller.extend({\n          someProp: null,\n                  init() {\n            this._super(...arguments);\n          },\n});\n      `,\n      errors: [\n        {\n          message: 'The \"someProp\" property should be above the \"init\" lifecycle hook on line 3',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code:\n        // whitespace is preserved inside `` and it's breaking the test\n        `import {inject as service} from '@ember/service';\n        export default Controller.extend({\n  queryParams: [],\n  currentUser: service(),\n});`,\n      output: `import {inject as service} from '@ember/service';\n        export default Controller.extend({\n  currentUser: service(),\n  queryParams: [],\n});`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Controller.extend({\n        customProp: { a: 1 },\n        aMethod() {\n          console.log('not empty');\n        }\n      });`,\n      output: `export default Controller.extend({\n        aMethod() {\n          console.log('not empty');\n        },\n              customProp: { a: 1 },\n});`,\n      options: [\n        {\n          order: ['method', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            'The \"aMethod\" method should be above the \"customProp\" custom property on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import Controller from '@ember/controller';\n        import { inject as service } from '@ember/service';\n        export default class UserController extends Controller {\n          queryParams = [];\n          @service currentUser;\n          customProp = 'test';\n        }`,\n      output: `import Controller from '@ember/controller';\n        import { inject as service } from '@ember/service';\n        export default class UserController extends Controller {\n          @service currentUser;\n          queryParams = [];\n          customProp = 'test';\n        }`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the \"queryParams\" property on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import Controller from '@ember/controller';\n        export default class UserController extends Controller {\n          customProp = 'test';\n          queryParams = [];\n          actions() {}\n        }`,\n      output: `import Controller from '@ember/controller';\n        export default class UserController extends Controller {\n          queryParams = [];\n          customProp = 'test';\n          actions() {}\n        }`,\n      errors: [\n        {\n          message: 'The \"queryParams\" property should be above the \"customProp\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `import Controller from '@ember/controller';\n        import { inject as service } from '@ember/service';\n        export default class UserController extends Controller {\n          @service currentUser;\n          queryParams = [];\n          actions() {}\n        }`,\n      output: `import Controller from '@ember/controller';\n        import { inject as service } from '@ember/service';\n        export default class UserController extends Controller {\n          queryParams = [];\n          @service currentUser;\n          actions() {}\n        }`,\n      options: [\n        {\n          order: ['query-params', 'service', 'single-line-function'],\n        },\n      ],\n      errors: [\n        {\n          message:\n            'The \"queryParams\" property should be above the \"currentUser\" service injection on line 4',\n          line: 5,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/order-in-models.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/order-in-models');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\neslintTester.run('order-in-models', rule, {\n  valid: [\n    'export default Model.extend();',\n    'export default Model.extend({ ...foo });',\n    `export default Model.extend({\n        shape: attr(\"string\"),\n        behaviors: hasMany(\"behaviour\"),\n        test: computed.alias(\"qwerty\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default Model.extend({\n        behaviors: hasMany(\"behaviour\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default Model.extend({\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default DS.Model.extend({\n        shape: DS.attr(\"string\"),\n        behaviors: hasMany(\"behaviour\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default DS.Model.extend({\n        behaviors: hasMany(\"behaviour\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default DS.Model.extend({\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default DS.Model.extend(TestMixin, {\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default DS.Model.extend(TestMixin, TestMixin2, {\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n    `export default DS.Model.extend({\n        a: attr(\"string\"),\n        b: belongsTo(\"c\", { async: false }),\n        convertA(paramA) {\n        }\n      });`,\n    {\n      code: `export default DS.Model.extend({\n        convertA(paramA) {\n        },\n        a: attr(\"string\"),\n        b: belongsTo(\"c\", { async: false }),\n      });`,\n      options: [\n        {\n          order: ['method'],\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend({\n        a: attr('string'),\n        convertA(paramA) {\n        },\n        customProp: { a: 1 }\n      });`,\n      options: [\n        {\n          order: ['attribute', 'method', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n        export default DS.Model.extend({\n          foo: service(),\n          a: attr('string'),\n          convertA(paramA) {\n          },\n          customProp: { a: 1 }\n        });`,\n      options: [{ order: ['service', 'attribute', 'method'] }],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    `import Model, { attr, hasMany } from '@ember-data/model';\n      export default class UserModel extends Model {\n        @attr('string') shape;\n        @hasMany('behaviour') behaviors;\n      }`,\n    `import UserModel, { attr, hasMany } from 'ember-data/model';\n      export default class AccountModel extends UserModel {\n        @attr('string') shape;\n        @hasMany('behaviour') behaviors;\n      }`,\n    `import Model, { attr, hasMany } from '@ember-data/model';\n      export default (class UserModel extends Model {\n        @attr('string') shape;\n        @hasMany('behaviour') behaviors;\n      });`,\n    `import Model, { attr, belongsTo, hasMany } from '@ember-data/model';\n      export default class UserModel extends Model {\n        @attr('string') name;\n        @belongsTo('team', { async: false }) team;\n        @hasMany('task', { async: true }) tasks;\n        isArchived = false;\n      }`,\n    {\n      code: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @service store;\n          @attr('string') name;\n          @hasMany('task', { async: true }) tasks;\n          customFlag = true;\n        }`,\n      options: [\n        {\n          order: ['service', 'attribute', 'relationship', 'property'],\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as emberService } from '@ember/service';\n        export default class UserModel extends Model {\n          @emberService store;\n          @attr('string') name;\n          @hasMany('task', { async: true }) tasks;\n          customFlag = true;\n        }`,\n      options: [\n        {\n          order: ['service', 'attribute', 'relationship', 'property'],\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @attr('string') name;\n          @hasMany('task', { async: true }) tasks;\n          @service store;\n          customFlag = true;\n        }`,\n      options: [\n        {\n          order: ['attribute', ['relationship', 'service'], 'property'],\n        },\n      ],\n    },\n    // spacing/indentation is intentionally not validated by this rule;\n    // only member ordering should matter.\n    `import Model, { attr, hasMany } from '@ember-data/model';\n      import { inject as service } from '@ember/service';\n\n      export default class UserModel extends Model {\n            @attr('string') name;\n        @hasMany('task', { async: true }) tasks;\n                 @service store;\n\n           customFlag = true;\n      }`,\n  ],\n  invalid: [\n    {\n      code: `export default Model.extend({\n        behaviors: hasMany(\"behaviour\"),\n        shape: attr(\"string\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n      output: `export default Model.extend({\n        shape: attr(\"string\"),\n        behaviors: hasMany(\"behaviour\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n      errors: [\n        {\n          message: 'The \"shape\" attribute should be above the \"behaviors\" relationship on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default Model.extend({\n        shape: attr(\"string\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        behaviors: hasMany(\"behaviour\")\n      });`,\n      output: `export default Model.extend({\n        shape: attr(\"string\"),\n        behaviors: hasMany(\"behaviour\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"behaviors\" relationship should be above the \"mood\" multi-line function on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Model.extend({\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        shape: attr(\"string\")\n      });`,\n      output: `export default Model.extend({\n        shape: attr(\"string\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message: 'The \"shape\" attribute should be above the \"mood\" multi-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend({\n        behaviors: hasMany(\"behaviour\"),\n        shape: DS.attr(\"string\"),\n        mood: Ember.computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n      output: `export default DS.Model.extend({\n        shape: DS.attr(\"string\"),\n        behaviors: hasMany(\"behaviour\"),\n        mood: Ember.computed(\"health\", \"hunger\", function() {\n        })\n      });`,\n      errors: [\n        {\n          message: 'The \"shape\" attribute should be above the \"behaviors\" relationship on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend({\n        shape: attr(\"string\"),\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        behaviors: hasMany(\"behaviour\")\n      });`,\n      output: `export default DS.Model.extend({\n        shape: attr(\"string\"),\n        behaviors: hasMany(\"behaviour\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"behaviors\" relationship should be above the \"mood\" multi-line function on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend({\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        shape: attr(\"string\")\n      });`,\n      output: `export default DS.Model.extend({\n        shape: attr(\"string\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message: 'The \"shape\" attribute should be above the \"mood\" multi-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend({\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        test: computed.alias(\"qwerty\")\n      });`,\n      output: `export default DS.Model.extend({\n        test: computed.alias(\"qwerty\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"test\" single-line function should be above the \"mood\" multi-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend(TestMixin, {\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        test: computed.alias(\"qwerty\")\n      });`,\n      output: `export default DS.Model.extend(TestMixin, {\n        test: computed.alias(\"qwerty\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"test\" single-line function should be above the \"mood\" multi-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend(TestMixin, TestMixin2, {\n        mood: computed(\"health\", \"hunger\", function() {\n        }),\n        test: computed.alias(\"qwerty\")\n      });`,\n      output: `export default DS.Model.extend(TestMixin, TestMixin2, {\n        test: computed.alias(\"qwerty\"),\n              mood: computed(\"health\", \"hunger\", function() {\n        }),\n});`,\n      errors: [\n        {\n          message:\n            'The \"test\" single-line function should be above the \"mood\" multi-line function on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default DS.Model.extend({\n        customProp: { a: 1 },\n        aMethod() {\n          console.log('not empty');\n        }\n      });`,\n      output: `export default DS.Model.extend({\n        aMethod() {\n          console.log('not empty');\n        },\n              customProp: { a: 1 },\n});`,\n      options: [\n        {\n          order: ['method', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            'The \"aMethod\" method should be above the \"customProp\" custom property on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr, hasMany } from '@ember-data/model';\n        export default class UserModel extends Model {\n          @hasMany('behaviour') behaviors;\n          @attr('string') shape;\n        }`,\n      output: `import Model, { attr, hasMany } from '@ember-data/model';\n        export default class UserModel extends Model {\n          @attr('string') shape;\n        @hasMany('behaviour') behaviors;\n          }`,\n      errors: [\n        {\n          message: 'The \"shape\" attribute should be above the \"behaviors\" relationship on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @service store;\n          @attr('string') name;\n        }`,\n      output: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @attr('string') name;\n        @service store;\n          }`,\n      errors: [\n        {\n          message: 'The \"name\" attribute should be above the \"store\" service injection on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr, belongsTo, hasMany } from '@ember-data/model';\n        export default class UserModel extends Model {\n          customFlag = true;\n          @attr('string') name;\n        }`,\n      output: `import Model, { attr, belongsTo, hasMany } from '@ember-data/model';\n        export default class UserModel extends Model {\n          @attr('string') name;\n        customFlag = true;\n          }`,\n      errors: [\n        {\n          message: 'The \"name\" attribute should be above the \"customFlag\" property on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @attr('string') name;\n          @service store;\n          @hasMany('task', { async: true }) tasks;\n          customFlag = true;\n        }`,\n      output: `import Model, { attr, hasMany } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @service store;\n          @attr('string') name;\n          @hasMany('task', { async: true }) tasks;\n          customFlag = true;\n        }`,\n      options: [\n        {\n          order: ['service', 'attribute', 'relationship', 'property'],\n        },\n      ],\n      errors: [\n        {\n          message: 'The \"store\" service injection should be above the \"name\" attribute on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import Model, { attr } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @attr('string') name;\n          customFlag = true;\n          @service store;\n        }`,\n      output: `import Model, { attr } from '@ember-data/model';\n        import { inject as service } from '@ember/service';\n        export default class UserModel extends Model {\n          @attr('string') name;\n          @service store;\n        customFlag = true;\n          }`,\n      options: [\n        {\n          order: ['attribute', ['relationship', 'service'], 'property'],\n        },\n      ],\n      errors: [\n        {\n          message:\n            'The \"store\" service injection should be above the \"customFlag\" property on line 5',\n          line: 6,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/order-in-routes.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/order-in-routes');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    babelOptions: {\n      configFile: require.resolve('../../../.babelrc'),\n    },\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\neslintTester.run('order-in-routes', rule, {\n  valid: [\n    'export default Route.extend();',\n    'export default Route.extend({ ...foo });',\n    `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        currentUser: service(),\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n        beforeModel() {},\n        model() {},\n        afterModel() {},\n        serialize() {},\n        redirect() {},\n        activate() {},\n        setupController() {},\n        renderTemplate() {},\n        resetController() {},\n        deactivate() {},\n        actions: {},\n        _customAction() { const foo = 'bar'; },\n        _customAction2: function() { const foo = 'bar'; },\n        tSomeTask: task(function* () {})\n      });`,\n    `import {inject} from '@ember/service';\n      export default Route.extend({\n        currentUser: inject(),\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n        beforeModel() {},\n        model() {},\n        afterModel() {},\n        actions: {},\n        _customAction() {},\n        _customAction2: function() {},\n        tSomeTask: task(function* () {})\n      });`,\n    `import {service} from '@ember/service';\n      export default Route.extend({\n        currentUser: service(),\n        queryParams: {},\n        customProp: \"test\",\n        model() {},\n        actions: {},\n      });`,\n    `export default Route.extend({\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n        model() {},\n        actions: {\n          test() { return this._customAction() }\n        },\n        _customAction() { const foo = 'bar'; }\n      });`,\n    `export default Route.extend({\n        init() {},\n        model() {},\n        render() {},\n      });`,\n    `export default Route.extend({\n        mergedProperties: {},\n        vehicle: alias(\"car\"),\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n        model() {},\n        actions: {}\n      });`,\n    `export default Route.extend({\n        mergedProperties: {},\n        test: \"asd\",\n        vehicle: alias(\"car\"),\n        model() {}\n      });`,\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        model() {},\n        beforeModel() {},\n        currentUser: service(),\n      });`,\n      options: [\n        {\n          order: ['model', 'lifecycle-hook', 'service'],\n        },\n      ],\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        deactivate() {},\n        beforeModel() {},\n        currentUser: service(),\n        model() {}\n      });`,\n      options: [\n        {\n          order: ['lifecycle-hook', 'service', 'model'],\n        },\n      ],\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        deactivate() {},\n        setupController() {},\n        beforeModel() {},\n        currentUser: service(),\n        model() {}\n      });`,\n      options: [\n        {\n          order: [['deactivate', 'setupController', 'beforeModel'], 'service', 'model'],\n        },\n      ],\n    },\n    `\n      import {inject as service} from '@ember/service';\n      export default Route.extend({\n        foo: service(),\n        init() {\n          this._super(...arguments);\n        },\n        actions: {}\n      });\n    `,\n    `\n      import {inject as service} from '@ember/service';\n      export default Route.extend({\n        foo: service(),\n        init() {\n          this._super(...arguments);\n        },\n        customFoo() {}\n      });\n    `,\n    {\n      code: `export default Route.extend({\n        prop: null,\n        actions: {\n          action: () => {}\n        },\n        customProp: { a: 1 }\n      });`,\n      options: [\n        {\n          order: ['property', 'actions', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    `import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n      export default class UserRoute extends Route {\n        @service currentUser;\n        queryParams = {};\n        customProp = 'test';\n        beforeModel() {}\n        model() {}\n      }`,\n    {\n      code: `import Route from '@ember/routing/route';\n        import { inject as service } from '@ember/service';\n        export default class UserRoute extends Route {\n          model() {}\n          beforeModel() {}\n          @service currentUser;\n        }`,\n      options: [\n        {\n          order: ['model', 'lifecycle-hook', 'service'],\n        },\n      ],\n    },\n    {\n      code: `import Route from '@ember/routing/route';\n        import { inject as service } from '@ember/service';\n        export default class UserRoute extends Route {\n          deactivate() {}\n          setupController() {}\n          beforeModel() {}\n          @service currentUser;\n          model() {}\n        }`,\n      options: [\n        {\n          order: [['deactivate', 'setupController', 'beforeModel'], 'service', 'model'],\n        },\n      ],\n    },\n    // spacing/indentation is intentionally not validated by this rule;\n    // only member ordering should matter.\n    `import Route from '@ember/routing/route';\n      import { inject as service } from '@ember/service';\n      export default class UserRoute extends Route {\n            @service currentUser;\n        queryParams = {};\n                 customProp = 'test';\n\n           beforeModel() {}\n      }`,\n  ],\n  invalid: [\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        queryParams: {},\n        currentUser: service(),\n        customProp: \"test\",\n        beforeModel() {},\n        model() {},\n        vehicle: alias(\"car\"),\n        actions: {},\n        _customAction() {}\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        currentUser: service(),\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        beforeModel() {},\n        model() {},\n        actions: {},\n        _customAction() {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the inherited \"queryParams\" property on line 3',\n          line: 4,\n        },\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"beforeModel\" lifecycle hook on line 6',\n          line: 8,\n        },\n      ],\n    },\n    {\n      code: `import {inject} from '@ember/service';\n      export default Route.extend({\n        queryParams: {},\n        currentUser: inject(),\n        customProp: \"test\",\n        beforeModel() {},\n        model() {},\n        vehicle: alias(\"car\"),\n        actions: {},\n        _customAction() {}\n      });`,\n      output: `import {inject} from '@ember/service';\n      export default Route.extend({\n        currentUser: inject(),\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        beforeModel() {},\n        model() {},\n        actions: {},\n        _customAction() {}\n      });`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the inherited \"queryParams\" property on line 3',\n          line: 4,\n        },\n        {\n          message:\n            'The \"vehicle\" single-line function should be above the \"beforeModel\" lifecycle hook on line 6',\n          line: 8,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        customProp: \"test\",\n        queryParams: {},\n        beforeModel() {},\n        model() {},\n        actions: {},\n        _customAction() {},\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      output: `export default Route.extend({\n        queryParams: {},\n        customProp: \"test\",\n        beforeModel() {},\n        model() {},\n        actions: {},\n        _customAction() {},\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        })\n      });`,\n      errors: [\n        {\n          message:\n            'The inherited \"queryParams\" property should be above the \"customProp\" property on line 2',\n          line: 3,\n        },\n        {\n          message:\n            'The \"levelOfHappiness\" multi-line function should be above the \"beforeModel\" lifecycle hook on line 4',\n          line: 8,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        customProp: \"test\",\n        queryParams: {},\n        model() {},\n        beforeModel() {},\n        actions: {},\n        _customAction() {}\n      });`,\n      output: `export default Route.extend({\n        queryParams: {},\n        customProp: \"test\",\n        model() {},\n        beforeModel() {},\n        actions: {},\n        _customAction() {}\n      });`,\n      errors: [\n        {\n          message:\n            'The inherited \"queryParams\" property should be above the \"customProp\" property on line 2',\n          line: 3,\n        },\n        {\n          message: 'The \"beforeModel\" lifecycle hook should be above the \"model\" hook on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        queryParams: {},\n        vehicle: alias(\"car\"),\n        customProp: \"test\",\n        model() {},\n        _customAction() { const foo = 'bar'; },\n        actions: {}\n      });`,\n      output: `export default Route.extend({\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        model() {},\n        actions: {},\n              _customAction() { const foo = 'bar'; },\n});`,\n      errors: [\n        {\n          message:\n            'The \"customProp\" property should be above the \"vehicle\" single-line function on line 3',\n          line: 4,\n        },\n        {\n          message: 'The actions hash should be above the \"_customAction\" method on line 6',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        model() {},\n        customProp: \"test\",\n        actions: {}\n      });`,\n      output: `export default Route.extend({\n        customProp: \"test\",\n        model() {},\n        actions: {}\n      });`,\n      errors: [\n        {\n          message: 'The \"customProp\" property should be above the \"model\" hook on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        test: \"asd\",\n        mergedProperties: {},\n        model() {}\n      });`,\n      output: `export default Route.extend({\n        mergedProperties: {},\n        test: \"asd\",\n        model() {}\n      });`,\n      errors: [\n        {\n          message:\n            'The inherited \"mergedProperties\" property should be above the \"test\" property on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        currentUser: service(),\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n        beforeModel() {},\n        model() {},\n        afterModel() {},\n        setupController() {},\n        redirect() {},\n        serialize() {},\n        activate() {},\n        deactivate() {},\n        renderTemplate() {},\n        resetController() {},\n        actions: {},\n        _customAction() {},\n        _customAction2: function() {},\n        tSomeTask: task(function* () {})\n      });`,\n      output: `import {inject as service} from '@ember/service';\n      export default Route.extend({\n        currentUser: service(),\n        queryParams: {},\n        customProp: \"test\",\n        vehicle: alias(\"car\"),\n        levelOfHappiness: computed(\"attitude\", \"health\", () => {\n        }),\n        beforeModel() {},\n        model() {},\n        afterModel() {},\n        redirect() {},\n        setupController() {},\n        serialize() {},\n        activate() {},\n        renderTemplate() {},\n        deactivate() {},\n        resetController() {},\n        actions: {},\n        _customAction() {},\n        _customAction2: function() {},\n        tSomeTask: task(function* () {})\n      });`,\n      errors: [\n        {\n          message:\n            'The \"redirect\" lifecycle hook should be above the \"setupController\" lifecycle hook on line 12',\n          line: 13,\n        },\n        {\n          message:\n            'The \"serialize\" lifecycle hook should be above the \"setupController\" lifecycle hook on line 12',\n          line: 14,\n        },\n        {\n          message:\n            'The \"activate\" lifecycle hook should be above the \"setupController\" lifecycle hook on line 12',\n          line: 15,\n        },\n        {\n          message:\n            'The \"renderTemplate\" lifecycle hook should be above the \"deactivate\" lifecycle hook on line 16',\n          line: 17,\n        },\n        {\n          message:\n            'The \"resetController\" lifecycle hook should be above the \"deactivate\" lifecycle hook on line 16',\n          line: 18,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        test: \"asd\",\n        _test2() { const foo = 'bar'; },\n        model() {}\n      });`,\n      output: `export default Route.extend({\n        test: \"asd\",\n        model() {},\n              _test2() { const foo = 'bar'; },\n});`,\n      errors: [\n        {\n          message: 'The \"model\" hook should be above the \"_test2\" method on line 3',\n          line: 4,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/routes/some-route.js',\n      code: `export default CustomRoute.extend({\n        model() {},\n        test: \"asd\",\n      });`,\n      output: `export default CustomRoute.extend({\n        test: \"asd\",\n              model() {},\n});`,\n      errors: [\n        {\n          message: 'The \"test\" property should be above the \"model\" hook on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/some-feature/route.js',\n      code: `export default CustomRoute.extend({\n        model() {},\n        test: \"asd\",\n      });`,\n      output: `export default CustomRoute.extend({\n        test: \"asd\",\n              model() {},\n});`,\n      errors: [\n        {\n          message: 'The \"test\" property should be above the \"model\" hook on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      filename: 'example-app/twisted-path/some-file.js',\n      code: `export default Route.extend({\n        model() {},\n        test: \"asd\",\n      });`,\n      output: `export default Route.extend({\n        test: \"asd\",\n              model() {},\n});`,\n      errors: [\n        {\n          message: 'The \"test\" property should be above the \"model\" hook on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Route.extend({\n          foo: service(),\n          actions: {},\n          init() {\n            this._super(...arguments);\n          }\n        });\n      `,\n      output: `\n        import {inject as service} from '@ember/service';\n        export default Route.extend({\n          foo: service(),\n          init() {\n            this._super(...arguments);\n          },\n                  actions: {},\n});\n      `,\n      errors: [\n        {\n          message: 'The \"init\" lifecycle hook should be above the actions hash on line 5',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Route.extend({\n          foo: service(),\n          customFoo() {},\n          init() {\n            this._super(...arguments);\n          },\n        });\n      `,\n      output: `\n        import {inject as service} from '@ember/service';\n        export default Route.extend({\n          foo: service(),\n          init() {\n            this._super(...arguments);\n          },\n                  customFoo() {},\n});\n      `,\n      errors: [\n        {\n          message:\n            'The \"init\" lifecycle hook should be above the \"customFoo\" empty method on line 5',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `\n        import {inject as service} from '@ember/service';\n        export default Route.extend({\n          init() {\n            this._super(...arguments);\n          },\n          foo: service()\n        });\n      `,\n      output: `\n        import {inject as service} from '@ember/service';\n        export default Route.extend({\n          foo: service(),\n                  init() {\n            this._super(...arguments);\n          },\n});\n      `,\n      errors: [\n        {\n          message:\n            'The \"foo\" service injection should be above the \"init\" lifecycle hook on line 4',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code: `\n        export default Route.extend({\n          init() {\n            this._super(...arguments);\n          },\n          someProp: null\n        });\n      `,\n      output: `\n        export default Route.extend({\n          someProp: null,\n                  init() {\n            this._super(...arguments);\n          },\n});\n      `,\n      errors: [\n        {\n          message: 'The \"someProp\" property should be above the \"init\" lifecycle hook on line 3',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        customProp: \"test\",\n        // queryParams line comment\n        queryParams: {},\n        model() {},\n        /**\n         * actions block comment\n         */\n        actions: {},\n        _customAction() {}\n      });`,\n      output: `export default Route.extend({\n        // queryParams line comment\n        queryParams: {},\n        customProp: \"test\",\n        model() {},\n        /**\n         * actions block comment\n         */\n        actions: {},\n        _customAction() {}\n      });`,\n      errors: [\n        {\n          message:\n            'The inherited \"queryParams\" property should be above the \"customProp\" property on line 2',\n          line: 4,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        customProp: \"test\",\n        model() {},\n        /**\n         * beforeModel block comment\n         */\n        beforeModel() {},\n        /**\n         * actions block comment\n         */\n        actions: {},\n        _customAction() {}\n      });`,\n      output: `export default Route.extend({\n        customProp: \"test\",\n        /**\n         * beforeModel block comment\n         */\n        beforeModel() {},\n        model() {},\n        /**\n         * actions block comment\n         */\n        actions: {},\n        _customAction() {}\n      });`,\n      errors: [\n        {\n          message: 'The \"beforeModel\" lifecycle hook should be above the \"model\" hook on line 3',\n          line: 7,\n        },\n      ],\n    },\n    {\n      code:\n        // whitespace is preserved inside `` and it's breaking the test\n        `export default Route.extend({\n  customProp: \"test\",\n  /**\n   * actions block comment\n   */\n  actions: {},\n  /**\n   * beforeModel block comment\n   */\n  beforeModel() {}\n});`,\n      output: `export default Route.extend({\n  customProp: \"test\",\n  /**\n   * beforeModel block comment\n   */\n  beforeModel() {},\n  /**\n   * actions block comment\n   */\n  actions: {},\n});`,\n      errors: [\n        {\n          message: 'The \"beforeModel\" lifecycle hook should be above the actions hash on line 6',\n          line: 10,\n        },\n      ],\n    },\n    {\n      code:\n        // whitespace is preserved inside `` and it's breaking the test\n        `export default Route.extend({\n  model() {},\n  test: \"asd\"\n});`,\n      output: `export default Route.extend({\n  test: \"asd\",\n  model() {},\n});`,\n      errors: [\n        {\n          message: 'The \"test\" property should be above the \"model\" hook on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n\n        model() {},\n\n        test: \"asd\",\n\n        actions: {}\n\n      });`,\n      output: `export default Route.extend({\n\n        test: \"asd\",\n\n        model() {},\n\n        actions: {}\n\n      });`,\n      errors: [\n        {\n          message: 'The \"test\" property should be above the \"model\" hook on line 3',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `export default Route.extend({\n        customProp: { a: 1 },\n        aMethod() {\n          console.log('not empty');\n        }\n      });`,\n      output: `export default Route.extend({\n        aMethod() {\n          console.log('not empty');\n        },\n              customProp: { a: 1 },\n});`,\n      options: [\n        {\n          order: ['method', 'custom:customProp'],\n        },\n      ],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [\n        {\n          message:\n            'The \"aMethod\" method should be above the \"customProp\" custom property on line 2',\n          line: 3,\n        },\n      ],\n    },\n    {\n      code: `import Route from '@ember/routing/route';\n        import { inject as service } from '@ember/service';\n        export default class UserRoute extends Route {\n          queryParams = {};\n          @service currentUser;\n          customProp = 'test';\n          beforeModel() {}\n          model() {}\n        }`,\n      output: `import Route from '@ember/routing/route';\n        import { inject as service } from '@ember/service';\n        export default class UserRoute extends Route {\n          @service currentUser;\n          queryParams = {};\n          customProp = 'test';\n          beforeModel() {}\n          model() {}\n        }`,\n      errors: [\n        {\n          message:\n            'The \"currentUser\" service injection should be above the inherited \"queryParams\" property on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import Route from '@ember/routing/route';\n        export default class UserRoute extends Route {\n          customProp = 'test';\n          queryParams = {};\n          model() {}\n          beforeModel() {}\n        }`,\n      output: `import Route from '@ember/routing/route';\n        export default class UserRoute extends Route {\n          queryParams = {};\n          customProp = 'test';\n          model() {}\n          beforeModel() {}\n        }`,\n      errors: [\n        {\n          message:\n            'The inherited \"queryParams\" property should be above the \"customProp\" property on line 3',\n          line: 4,\n        },\n        {\n          message: 'The \"beforeModel\" lifecycle hook should be above the \"model\" hook on line 5',\n          line: 6,\n        },\n      ],\n    },\n    {\n      code: `import Route from '@ember/routing/route';\n        export default class UserRoute extends Route {\n          customProp = 'test';\n          model() {}\n          beforeModel() {}\n        }`,\n      output: `import Route from '@ember/routing/route';\n        export default class UserRoute extends Route {\n          customProp = 'test';\n          beforeModel() {}\n        model() {}\n          }`,\n      errors: [\n        {\n          message: 'The \"beforeModel\" lifecycle hook should be above the \"model\" hook on line 4',\n          line: 5,\n        },\n      ],\n    },\n    {\n      code: `import Route from '@ember/routing/route';\n        import { inject as service } from '@ember/service';\n        export default class UserRoute extends Route {\n          model() {}\n          beforeModel() {}\n          @service currentUser;\n          queryParams = {};\n        }`,\n      output: `import Route from '@ember/routing/route';\n        import { inject as service } from '@ember/service';\n        export default class UserRoute extends Route {\n          @service currentUser;\n          model() {}\n          beforeModel() {}\n          queryParams = {};\n        }`,\n      options: [\n        {\n          order: ['service', 'model', 'lifecycle-hook', 'inherited-property'],\n        },\n      ],\n      errors: [\n        {\n          message: 'The \"currentUser\" service injection should be above the \"model\" hook on line 4',\n          line: 6,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/prefer-ember-test-helpers.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/prefer-ember-test-helpers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst TEST_FILE_NAME = 'some-test.js';\nconst REGULAR_FILE_NAME = 'regular-file.js';\n\nruleTester.run('prefer-ember-test-helpers', rule, {\n  valid: [\n    // Native methods in regular files\n    {\n      filename: REGULAR_FILE_NAME,\n      code: \"blur('.some-element');\",\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: REGULAR_FILE_NAME,\n      code: \"find('.some-element');\",\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: REGULAR_FILE_NAME,\n      code: \"focus('.some-element');\",\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Ember test helper method properly imported\n    {\n      filename: TEST_FILE_NAME,\n      code: `\n      import { blur } from '@ember/test-helpers';\n      import foo1, { foo2 } from 'bar';\n\n      test('foo', async (assert) => {\n        await blur('.some-element');\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `\n      import { find } from '@ember/test-helpers';\n      import foo1, { foo2 } from 'bar';\n\n      test('foo', async (assert) => {\n        await find('.some-element');\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `\n      import { focus } from '@ember/test-helpers';\n      import foo1, { foo2 } from 'bar';\n\n      test('foo', async (assert) => {\n        await focus('.some-element');\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Wrong method on import from Ember test helpers\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { blur } from '@ember/test-helpers';\n\n      test('foo', async (assert) => {\n        await blur.wrongFunction();\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { find } from '@ember/test-helpers';\n\n      test('foo', async (assert) => {\n        await find.wrongFunction();\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { focus } from '@ember/test-helpers';\n\n      test('foo', async (assert) => {\n        await focus.wrongFunction();\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Method on unrelated object called\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { blur } from '@ember/test-helpers';\n\n      test('foo', async (assert) => {\n        await WrongObject.blur();\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { find } from '@ember/test-helpers';\n\n      test('foo', async (assert) => {\n        await WrongObject.find();\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { focus } from '@ember/test-helpers';\n\n      test('foo', async (assert) => {\n        await WrongObject.focus();\n      });`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Method properly imported from Ember test helpers with aliased name\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { blur as myBlurName } from '@ember/test-helpers';\n\n      myBlurName();`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { find as myFindName } from '@ember/test-helpers';\n\n      myFindName();`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { focus as myFocusName } from '@ember/test-helpers';\n\n      myFocusName();`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Method imported from any source\n    {\n      filename: TEST_FILE_NAME,\n      code: `import blur from 'irrelevant-import-path';\n      blur('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { blur } from 'irrelevant-import-path';\n      blur('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { something as blur } from 'irrelevant-import-path';\n      blur('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import find from 'irrelevant-import-path';\n      find('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { find } from 'irrelevant-import-path';\n      find('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { something as find } from 'irrelevant-import-path';\n      find('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import focus from 'irrelevant-import-path';\n      focus('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { focus } from 'irrelevant-import-path';\n      focus('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `import { something as focus } from 'irrelevant-import-path';\n      focus('.some-element');`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Function declaration within test file\n    {\n      filename: TEST_FILE_NAME,\n      code: `function blur(el) { console.log('blurring from element!'); }\n\n      blur('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `function find(el) { console.log('finding element!'); }\n\n      find('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `function focus(el) { console.log('focusing element!'); }\n\n      focus('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Function expression within test file\n    {\n      filename: TEST_FILE_NAME,\n      code: `const blur = function(el) { console.log('blurring from element!'); }\n\n      blur('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `const find = function(el) { console.log('finding element!'); }\n\n      find('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `const focus = function(el) { console.log('focusing element!'); }\n\n      focus('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Arrow Function declaration within test file\n    {\n      filename: TEST_FILE_NAME,\n      code: `const blur = (el) => { console.log('blurring from element!'); }\n\n      blur('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `const find = (el) => { console.log('finding element!'); }\n\n      find('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `const focus = (el) => { console.log('focusing element!'); }\n\n      focus('.some-element')`,\n      globals: { blur: true, find: true, focus: true },\n    },\n\n    // Without globals:\n    {\n      filename: TEST_FILE_NAME,\n      code: \"focus('.some-element');\",\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: \"const focus = () => {}; focus('.some-element');\",\n    },\n  ],\n\n  invalid: [\n    {\n      filename: TEST_FILE_NAME,\n      code: `test('foo', async (assert) => {\n        await blur('.some-element');\n      });`,\n      output: null,\n      globals: { blur: true, find: true, focus: true },\n      errors: [\n        {\n          message: 'Import the `blur()` method from @ember/test-helpers',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `test('foo', async (assert) => {\n        await find('.some-element');\n      });`,\n      output: null,\n      globals: { blur: true, find: true, focus: true },\n      errors: [\n        {\n          message: 'Import the `find()` method from @ember/test-helpers',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      filename: TEST_FILE_NAME,\n      code: `test('foo', async (assert) => {\n        await focus('.some-element');\n      });`,\n      output: null,\n      globals: { blur: true, find: true, focus: true },\n      errors: [\n        {\n          message: 'Import the `focus()` method from @ember/test-helpers',\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    {\n      // Aliased relevant import but didn't use it.\n      filename: TEST_FILE_NAME,\n      code: `\n      import { blur as myBlurName } from '@ember/test-helpers';\n      test('foo', async (assert) => {\n        await blur('.some-element');\n      });`,\n      output: null,\n      globals: { blur: true, find: true, focus: true },\n      errors: [\n        {\n          message: 'Import the `blur()` method from @ember/test-helpers',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Aliased relevant import but didn't use it.\n      filename: TEST_FILE_NAME,\n      code: `\n      import { find as myFindName } from '@ember/test-helpers';\n      test('foo', async (assert) => {\n        await find('.some-element');\n      });`,\n      output: null,\n      globals: { blur: true, find: true, focus: true },\n      errors: [\n        {\n          message: 'Import the `find()` method from @ember/test-helpers',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Aliased relevant import but didn't use it.\n      filename: TEST_FILE_NAME,\n      code: `\n      import { focus as myFocusName } from '@ember/test-helpers';\n      test('foo', async (assert) => {\n        await focus('.some-element');\n      });`,\n      output: null,\n      globals: { blur: true, find: true, focus: true },\n      errors: [\n        {\n          message: 'Import the `focus()` method from @ember/test-helpers',\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/require-async-inverse-relationship.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/require-async-inverse-relationship');\nconst RuleTester = require('eslint').RuleTester;\n\nconst parserOptions = { ecmaVersion: 2022, sourceType: 'module' };\n\nconst ruleTester = new RuleTester({\n  parserOptions,\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nruleTester.run('require-async-inverse-relationship', rule, {\n  valid: [\n    `import Model, { belongsTo } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post', { async: true, inverse: 'comments' }) post;\n      }`,\n    `import Model, { hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @hasMany('comment', { async: true, inverse: 'post' }) comments;\n      }`,\n    `import Model, { belongsTo, hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post', { async: false, inverse: 'comments' }) post;\n        @hasMany('user', { async: true, inverse: null }) owner;\n      }`,\n  ],\n\n  invalid: [\n    {\n      code: `import Model, { belongsTo } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post') post;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @belongsTo decorator requires an `async` property to be specified.',\n          type: 'CallExpression',\n        },\n        {\n          message: 'The @belongsTo decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { belongsTo } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post', {}) post;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @belongsTo decorator requires an `async` property to be specified.',\n          type: 'CallExpression',\n        },\n        {\n          message: 'The @belongsTo decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { belongsTo } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post', { async: 'comments'}) post;\n      }`,\n      output: null,\n      errors: [\n        {\n          message:\n            'The @belongsTo decorator requires an `async` property to be specified as a boolean.',\n          type: 'CallExpression',\n        },\n        {\n          message: 'The @belongsTo decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { belongsTo } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post', { async: true }) post;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @belongsTo decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { belongsTo } from '@ember-data/model';\n\n      export default class extends Model {\n        @belongsTo('post', { inverse: 'comments' }) post;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @belongsTo decorator requires an `async` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @hasMany('comment') comments;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @hasMany decorator requires an `async` property to be specified.',\n          type: 'CallExpression',\n        },\n        {\n          message: 'The @hasMany decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @hasMany('comment', {}) comments;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @hasMany decorator requires an `async` property to be specified.',\n          type: 'CallExpression',\n        },\n        {\n          message: 'The @hasMany decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @hasMany('comment', { async: 'comments'}) comments;\n      }`,\n      output: null,\n      errors: [\n        {\n          message:\n            'The @hasMany decorator requires an `async` property to be specified as a boolean.',\n          type: 'CallExpression',\n        },\n        {\n          message: 'The @hasMany decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @hasMany('comment', { async: true }) comments;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @hasMany decorator requires an `inverse` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `import Model, { hasMany } from '@ember-data/model';\n\n      export default class extends Model {\n        @hasMany('comment', { inverse: 'post' }) comments;\n      }`,\n      output: null,\n      errors: [\n        {\n          message: 'The @hasMany decorator requires an `async` property to be specified.',\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/require-computed-macros.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/require-computed-macros');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\nconst {\n  ERROR_MESSAGE_READS,\n  ERROR_MESSAGE_AND,\n  ERROR_MESSAGE_OR,\n  ERROR_MESSAGE_GT,\n  ERROR_MESSAGE_GTE,\n  ERROR_MESSAGE_LT,\n  ERROR_MESSAGE_LTE,\n  ERROR_MESSAGE_NOT,\n  ERROR_MESSAGE_EQUAL,\n  ERROR_MESSAGE_FILTER_BY,\n  ERROR_MESSAGE_MAP_BY,\n} = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\n\nruleTester.run('require-computed-macros', rule, {\n  valid: [\n    'computed()',\n    'computed(true)',\n    'computed(function() {})',\n    'computed(function() { return; })',\n    'computed(function() { someCall(); return this.x; })', // Multiple statements in function body.\n    'computed(function() { return this.x; }, SOME_OTHER_ARG)', // Function isn't last arg.\n    'computed(function() { notAReturnStatement(); })',\n    'other(function() { return this.x; })',\n\n    // READS\n    \"reads('x')\",\n    'computed(function() { return this; })',\n    'computed(function() { return SOME_VAR; })',\n    'computed(function() { return this.get(SOME_VAR); })',\n    'computed(function() { return this.prop[123]; })',\n    'computed(function() { return this.prop[i]; })',\n    'computed(function() { return this.someFunction(); })',\n    'computed(function() { return this.prop.someFunction(); })',\n\n    // AND\n    \"and('x', 'y')\",\n    'computed(function() { return SOME_VAR && OTHER_VAR; })',\n    'computed(function() { return this.x && this.y || this.z; })', // Mixed operators.\n    'computed(function() { return 123 && this.x; })', // With a Literal.\n    'computed(function() { return this.x && 123; })', // With a Literal.\n    'computed(function() { return this.get(\"x\") && this.get(\"y\") || this.get(\"z\"); })', // Mixed operators (and this.get)\n\n    // OR\n    \"or('x', 'y')\",\n    'computed(function() { return SOME_VAR || OTHER_VAR; })',\n\n    // GT\n    \"gt('x', 123)\",\n    'computed(function() { return SOME_VAR > OTHER_VAR; })',\n    'computed(function() { return this.x > this.y; })',\n\n    // GTE\n    \"gte('x', 123)\",\n    'computed(function() { return SOME_VAR >= OTHER_VAR; })',\n\n    // LT\n    \"lt('x', 123)\",\n    'computed(function() { return SOME_VAR < OTHER_VAR; })',\n\n    // LTE\n    \"lte('x', 123)\",\n    'computed(function() { return SOME_VAR <= OTHER_VAR; })',\n\n    // NOT\n    \"not('x')\",\n    'computed(function() { return !SOME_VAR; })',\n\n    // EQUAL\n    \"equal('x', 123)\",\n    'computed(function() { return SOME_VAR === 123; })',\n    'computed(function() { return SOME_VAR === \"Hello\"; })',\n    'computed(function() { return this.prop === MY_VAR; })',\n    \"computed(function() { return this.get('prop') === MY_VAR; })\",\n    'computed(function() { return this.prop === this.otherProp; })',\n\n    // FILTERBY\n    \"filterBy('chores', 'done', true)\",\n    'computed(function() { return this.chores.filterBy(this.otherProp, true); })', // Ignored because value depends on function's `this`.\n    \"computed(function() { return this.chores.filterBy('done', this.otherProp); })\", // Ignored because value depends on function's `this`.\n\n    // MAPBY\n    \"mapBy('children', 'age')\",\n    \"computed(function() { return this.children?.mapBy('age'); })\", // Ignored because function might not exist.\n    \"computed(function() { return this.nested?.children.mapBy('age'); })\", // Ignored because function might not exist.\n    'computed(function() { return this.children.mapBy(this.otherProp); })', // Ignored because value depends on function's `this`.\n    'computed(function() { return this.children.mapBy(someFunction(this.otherProp)); })', // Ignored because value depends on function's `this`.\n\n    // Decorator (these are ignored when the `includeNativeGetters` option is off):\n    \"class Test { @computed('x') get someProp() { return this.x; } }\",\n    'class Test { @computed() get someProp() { return this.x && this.y; } }',\n    'class Test { @computed() get someProp() { return this.x > 123; } }',\n    \"class Test { @computed() get someProp() { return this.children.mapBy('age'); } }\",\n  ],\n  invalid: [\n    // READS\n    {\n      code: 'computed(function() { return this.x; })',\n      output: \"computed.reads('x')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      code: \"import Ember from 'ember'; Ember.computed(function() { return this.x; })\",\n      output: \"import Ember from 'ember'; computed.reads('x')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { return this.x.y; })', // Nested path.\n      output: \"computed.reads('x.y')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      code: \"computed(function() { return this.get('x.y'); })\", // this.get()\n      output: \"computed.reads('x.y')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      code: \"computed('x', function() { return this.x; })\", // With dependent key.\n      output: \"computed.reads('x')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      // Optional chaining.\n      code: 'computed(function() { return this.x?.y?.z; })',\n      output: \"computed.reads('x.y.z')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      // Optional chaining unnecessarily used on `this`.\n      code: 'computed(function() { return this?.x?.y?.z; })',\n      output: \"computed.reads('x.y.z')\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n    {\n      // Decorator:\n      code: \"class Test { @computed('x') get someProp() { return this.x; } }\",\n      options: [{ includeNativeGetters: true }],\n      output: \"class Test { @computed.reads('x') someProp }\",\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'MethodDefinition' }],\n    },\n\n    // READS - self-referential (no autofix)\n    {\n      code: \"({ storeIds: computed('storeIds', function() { return this.storeIds; }) })\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE_READS, type: 'CallExpression' }],\n    },\n\n    // AND\n    {\n      code: 'computed(function() { return this.x && this.y; })',\n      output: \"computed.and('x', 'y')\",\n      errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { return this.x && this.y && this.z; })', // Three parts.\n      output: \"computed.and('x', 'y', 'z')\",\n      errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],\n    },\n    {\n      code: 'computed(function() { return this.x && this.y.z && this.w; })', // Three parts with a nested path.\n      output: \"computed.and('x', 'y.z', 'w')\",\n      errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],\n    },\n    {\n      code: \"computed(function() { return this.get('x') && this.get('y.z') && this.w; })\", // Three parts with a nested path (and this.get).\n      output: \"computed.and('x', 'y.z', 'w')\",\n      errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],\n    },\n    {\n      // Optional chaining.\n      code: 'computed(function() { return this.x?.y && this.z; })',\n      output: \"computed.and('x.y', 'z')\",\n      errors: [{ message: ERROR_MESSAGE_AND, type: 'CallExpression' }],\n    },\n    {\n      // Decorator:\n      code: 'class Test { @computed() get someProp() { return this.x && this.y; } }',\n      options: [{ includeNativeGetters: true }],\n      output: \"class Test { @computed.and('x', 'y') someProp }\",\n      errors: [{ message: ERROR_MESSAGE_AND, type: 'MethodDefinition' }],\n    },\n\n    // OR\n    {\n      code: 'computed(function() { return this.x || this.y; })',\n      output: \"computed.or('x', 'y')\",\n      errors: [{ message: ERROR_MESSAGE_OR, type: 'CallExpression' }],\n    },\n\n    // GT\n    {\n      code: 'computed(function() { return this.x > 123; })',\n      output: \"computed.gt('x', 123)\",\n      errors: [{ message: ERROR_MESSAGE_GT, type: 'CallExpression' }],\n    },\n    {\n      // Optional chaining:\n      code: 'computed(function() { return this.x?.y > 123; })',\n      output: \"computed.gt('x.y', 123)\",\n      errors: [{ message: ERROR_MESSAGE_GT, type: 'CallExpression' }],\n    },\n    {\n      // Decorator:\n      code: 'class Test { @computed() get someProp() { return this.x > 123; } }',\n      options: [{ includeNativeGetters: true }],\n      output: \"class Test { @computed.gt('x', 123) someProp }\",\n      errors: [{ message: ERROR_MESSAGE_GT, type: 'MethodDefinition' }],\n    },\n\n    // GTE\n    {\n      code: 'computed(function() { return this.x >= 123; })',\n      output: \"computed.gte('x', 123)\",\n      errors: [{ message: ERROR_MESSAGE_GTE, type: 'CallExpression' }],\n    },\n\n    // LT\n    {\n      code: 'computed(function() { return this.x < 123; })',\n      output: \"computed.lt('x', 123)\",\n      errors: [{ message: ERROR_MESSAGE_LT, type: 'CallExpression' }],\n    },\n\n    // LTE\n    {\n      code: 'computed(function() { return this.x <= 123; })',\n      output: \"computed.lte('x', 123)\",\n      errors: [{ message: ERROR_MESSAGE_LTE, type: 'CallExpression' }],\n    },\n\n    // NOT\n    {\n      code: 'computed(function() { return !this.x; })',\n      output: \"computed.not('x')\",\n      errors: [{ message: ERROR_MESSAGE_NOT, type: 'CallExpression' }],\n    },\n\n    // EQUAL\n    {\n      code: 'computed(function() { return this.x === 123; })',\n      output: \"computed.equal('x', 123)\",\n      errors: [{ message: ERROR_MESSAGE_EQUAL, type: 'CallExpression' }],\n    },\n    {\n      code: \"computed(function() { return this.get('x') === 123; })\", // this.get()\n      output: \"computed.equal('x', 123)\",\n      errors: [{ message: ERROR_MESSAGE_EQUAL, type: 'CallExpression' }],\n    },\n\n    // FILTERBY\n    {\n      code: \"computed(function() { return this.chores.filterBy('done', true); })\",\n      output: \"computed.filterBy('chores', 'done', true)\",\n      errors: [{ message: ERROR_MESSAGE_FILTER_BY, type: 'CallExpression' }],\n    },\n\n    // MAPBY\n    {\n      code: \"computed(function() { return this.children.mapBy('age'); })\",\n      output: \"computed.mapBy('children', 'age')\",\n      errors: [{ message: ERROR_MESSAGE_MAP_BY, type: 'CallExpression' }],\n    },\n    {\n      code: \"computed(function() { return this.nested.children.mapBy('age'); })\",\n      output: \"computed.mapBy('nested.children', 'age')\",\n      errors: [{ message: ERROR_MESSAGE_MAP_BY, type: 'CallExpression' }],\n    },\n    {\n      // Decorator:\n      code: \"class Test { @computed() get someProp() { return this.children.mapBy('age'); } }\",\n      options: [{ includeNativeGetters: true }],\n      output: \"class Test { @computed.mapBy('children', 'age') someProp }\",\n      errors: [{ message: ERROR_MESSAGE_MAP_BY, type: 'MethodDefinition' }],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/require-computed-property-dependencies.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/require-computed-property-dependencies');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE_NON_STRING_VALUE } = rule;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\nruleTester.run('require-computed-property-dependencies', rule, {\n  valid: [\n    \"import Ember from 'ember'; Ember.computed();\",\n    \"import Ember from 'ember'; Ember.computed(function() {});\",\n    \"import Ember from 'ember'; Ember.computed('unused', function() {});\",\n    // Volatile:\n    \"import Ember from 'ember'; Ember.computed('name', function() { return this.get('name'); }).volatile()\",\n    // ES5 getter usage:\n    \"import Ember from 'ember'; Ember.computed('name', function() { return this.name; });\",\n    \"import Ember from 'ember'; Ember.computed('name', function() { return this.get('name'); });\",\n    // Does not throw with node type (ClassProperty) not handled by estraverse.\n    \"import Ember from 'ember'; Ember.computed('name', function() { class Foo { @someDecorator() someProp } });\",\n    // String concatenation in dependent key:\n    \"import Ember from 'ember';  Ember.computed('na' + 'me', function() { return this.get('name'); });\",\n    // Optional chaining:\n    \"import Ember from 'ember'; Ember.computed(function() { return this?.someFunction(); });\",\n    \"import Ember from 'ember'; Ember.computed('x.y', function() { return this?.x?.y });\",\n    // Without `Ember.`:\n    \"import { computed } from '@ember/object'; computed('name', function() {return this.get('name');});\",\n    `\n      import Ember from 'ember';\n      Ember.computed('list.@each.foo', function() {\n        return this.get('list').map(function(item) {\n          return item.get('foo');\n        });\n      });\n    `,\n    \"import Ember from 'ember'; Ember.computed('list.[]', function() { return this.get('list.length');});\",\n    \"import Ember from 'ember'; Ember.computed('deeper.than.needed.but.okay', function() { return this.get('deeper'); });\",\n    \"import Ember from 'ember'; Ember.computed('array.[]', function() { return this.get('array.firstObject'); });\",\n    \"import Ember from 'ember'; Ember.computed('array.[]', function() { return this.get('array.lastObject'); });\",\n    \"import Ember from 'ember'; Ember.computed('foo.{bar,baz}', function() { return this.get('foo.bar') + this.get('foo.baz'); });\",\n    \"import Ember from 'ember'; Ember.computed('foo.@each.{bar,baz}', function() { return this.get('foo').mapBy('bar') + this.get('foo').mapBy('bar'); });\",\n    // Array inside braces:\n    `\n      import Ember from 'ember';\n      Ember.computed('article.{comments.[],title}', function() {\n        return this.article.title + someFunction(this.article.comments.mapBy(function(comment) { return comment.author; }));\n      });\n    `,\n    // Nesting inside braces:\n    `\n      import Ember from 'ember';\n      Ember.computed('article.{comments.innerProperty,title}', function() {\n        return this.article.title + someFunction(this.article.comments.innerProperty);\n      });\n    `,\n    // Braces but no nesting:\n    `\n      import Ember from 'ember';\n      Ember.computed('{foo,bar}', function() {\n        return this.get('foo') + this.get('bar');\n      });\n    `,\n    // Computed macro that should be ignored:\n    \"import Ember from 'ember'; Ember.computed.someMacro(function() { return this.x; })\",\n    \"import Ember from 'ember'; Ember.computed.someMacro('test')\",\n    // Dynamic key:\n    \"import Ember from 'ember'; Ember.computed(dynamic, function() {});\",\n    // Dynamic key:\n    \"import Ember from 'ember'; Ember.computed(...PROPERTIES, function() {});\",\n    // Incorrect usage that should be ignored:\n    \"import Ember from 'ember'; Ember.computed(123)\",\n    // Should ignore injected service names:\n    `\n      import Ember from 'ember';\n      import Component from '@ember/component';\n      import { inject as service } from '@ember/service';\n      Component.extend({\n        intl: service(),\n        myProperty: Ember.computed('name', function() {\n          console.log(this.intl);\n          return this.name + this.intl.t('some.translation.key');\n          console.log(this.otherService);\n          console.log(this.serviceNameInStringLiteral);\n        }),\n        otherService: service(), // Service injection coming after computed property.\n        'serviceNameInStringLiteral': service() // Property name as string literal.\n      });\n    `,\n    // Should ignore injected service names via `service` method:\n    `\n      import Ember from 'ember';\n      import Component from '@ember/component';\n      import { service } from '@ember/service';\n      Component.extend({\n        intl: service(),\n        myProperty: Ember.computed('name', function() {\n          console.log(this.intl);\n          return this.name + this.intl.t('some.translation.key');\n          console.log(this.otherService);\n          console.log(this.serviceNameInStringLiteral);\n        }),\n        otherService: service(), // Service injection coming after computed property.\n        'serviceNameInStringLiteral': service() // Property name as string literal.\n      });\n    `,\n    // Should ignore the left side of an assignment.\n    \"import Ember from 'ember'; Ember.computed('right', function() { this.left = this.right; })\",\n    // Should ignore the left side of an assignment with nested path.\n    \"import Ember from 'ember'; Ember.computed('right', function() { this.left1.left2 = this.right; })\",\n    // Explicit getter function:\n    `\n      import { computed } from '@ember/object';\n      computed('firstName', 'lastName', {\n        get() {\n          return this.firstName + ' ' + this.lastName;\n        },\n        set(key, value) {}\n      })\n    `,\n    // Decorator:\n    `\n      import { computed } from '@ember/object';\n      class Test {\n        @computed('first', 'last')\n        get fullName() { return this.first + ' ' + this.last; }\n      }\n    `,\n    // Decorator:\n    `\n      import { computed } from '@ember/object';\n      import { inject as service } from '@ember/service';\n      class Test {\n        @service i18n; // Service names not required as dependent keys by default.\n        @service 'serviceNameInStringLiteral';\n\n        @computed('first', 'last')\n        get fullName() { return this.i18n.t(this.first + ' ' + this.last); }\n\n        @computed()\n        get foo() { return this.serviceNameInStringLiteral; }\n      }\n    `,\n  ],\n  invalid: [\n    // Dynamic key:\n    {\n      code: \"import Ember from 'ember'; Ember.computed(dynamic, function() {});\",\n      output: null,\n      options: [{ allowDynamicKeys: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_NON_STRING_VALUE,\n          type: 'Identifier',\n        },\n      ],\n    },\n    // Dynamic keys:\n    {\n      code: \"import Ember from 'ember'; Ember.computed(...PROPERTIES, function() {});\",\n      output: null,\n      options: [{ allowDynamicKeys: false }],\n      errors: [\n        {\n          message: ERROR_MESSAGE_NON_STRING_VALUE,\n          type: 'SpreadElement',\n        },\n      ],\n    },\n    // Dynamic key with missing dependency:\n    {\n      code: \"import Ember from 'ember'; Ember.computed(dynamic, function() { return this.undeclared; });\",\n      output:\n        \"import Ember from 'ember'; Ember.computed(dynamic, 'undeclared', function() { return this.undeclared; });\",\n      options: [{ allowDynamicKeys: false }],\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n        {\n          message: ERROR_MESSAGE_NON_STRING_VALUE,\n          type: 'Identifier',\n        },\n      ],\n    },\n    // Multiple dynamic (identifier and spread) keys with missing dependency:\n    {\n      code: \"import Ember from 'ember'; Ember.computed(dynamic, ...moreDynamic, function() { return this.undeclared; });\",\n      output:\n        \"import Ember from 'ember'; Ember.computed(dynamic, ...moreDynamic, 'undeclared', function() { return this.undeclared; });\",\n      options: [{ allowDynamicKeys: false }],\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n        {\n          message: ERROR_MESSAGE_NON_STRING_VALUE,\n          type: 'Identifier',\n        },\n        {\n          message: ERROR_MESSAGE_NON_STRING_VALUE,\n          type: 'SpreadElement',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return Ember.get(this, 'undeclared');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', function() {\n          return Ember.get(this, 'undeclared');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.get('undeclared') + this.get('undeclared2');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', 'undeclared2', function() {\n          return this.get('undeclared') + this.get('undeclared2');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared, undeclared2',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Volatile:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.get('undeclared');\n        }).volatile();\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', function() {\n          return this.get('undeclared');\n        }).volatile();\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // ES5 getter usage:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.undeclared + this.undeclared2;\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', 'undeclared2', function() {\n          return this.undeclared + this.undeclared2;\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared, undeclared2',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // ES5 getter usage inside function call:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return someFunction(this.undeclared) + some.thing(this.undeclared2) + some(this.undeclared3).thing;\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', 'undeclared2', 'undeclared3', function() {\n          return someFunction(this.undeclared) + some.thing(this.undeclared2) + some(this.undeclared3).thing;\n        });\n      `,\n      errors: [\n        {\n          message:\n            'Use of undeclared dependencies in computed property: undeclared, undeclared2, undeclared3',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // ES5 getter usage inside array index:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return someArray[this.undeclared];\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', function() {\n          return someArray[this.undeclared];\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // ES5 getter usage with nesting:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.a.b.c;\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('a.b.c', function() {\n          return this.a.b.c;\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: a.b.c',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // ES5 getter usage with array with numeric index:\n    // TODO: an improvement would be to detect the missing `someArray.[]` dependency key.\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.someArray[123];\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('someArray', function() {\n          return this.someArray[123];\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: someArray',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // ES5 getter usage with array/object access:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.someArrayOrObject[index];\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('someArrayOrObject', function() {\n          return this.someArrayOrObject[index];\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: someArrayOrObject',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // With function calls on properties.\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.get('service1').someFunction() + this.service2.someFunction();\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('service1', 'service2', function() {\n          return this.get('service1').someFunction() + this.service2.someFunction();\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: service1, service2',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Without `Ember.`:\n      code: `\n        import { computed } from '@ember/object';\n        computed(function() {\n          return this.get('undeclared') + this.get('undeclared2');\n        });\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        computed('undeclared', 'undeclared2', function() {\n          return this.get('undeclared') + this.get('undeclared2');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared, undeclared2',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.get('undeclared') + this.get('undeclared');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('undeclared', function() {\n          return this.get('undeclared') + this.get('undeclared');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.get('foo.bar.baz');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('foo.bar.baz', function() {\n          return this.get('foo.bar.baz');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo.bar.baz',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Ensure that the redundant key (`foo`) is removed.\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed('foo', function() {\n          return this.get('foo.bar.baz');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('foo.bar.baz', function() {\n          return this.get('foo.bar.baz');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo.bar.baz',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.getProperties('a', dynamic, 'b', 'c');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('a', 'b', 'c', function() {\n          return this.getProperties('a', dynamic, 'b', 'c');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: a, b, c',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.getProperties(['a', dynamic, 'b', 'c']);\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('a', 'b', 'c', function() {\n          return this.getProperties(['a', dynamic, 'b', 'c']);\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: a, b, c',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return Ember.getProperties(this, ['a', dynamic, 'b', 'c']);\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('a', 'b', 'c', function() {\n          return Ember.getProperties(this, ['a', dynamic, 'b', 'c']);\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: a, b, c',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.getWithDefault('maybe', {});\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('maybe', function() {\n          return this.getWithDefault('maybe', {});\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: maybe',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return Ember.getWithDefault(this, 'maybe', {});\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('maybe', function() {\n          return Ember.getWithDefault(this, 'maybe', {});\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: maybe',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed('constructor.bar', function() {\n          return this.get('constructor.bar') + this.get('constructor.baz');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('constructor.{bar,baz}', function() {\n          return this.get('constructor.bar') + this.get('constructor.baz');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: constructor.baz',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Ensure that the fixer removes the redundant `quux.length` dependency.\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed('foo.bar', 'quux.[]', 'quux.length', function() {\n          return this.get('foo.bar') + this.get('foo.baz') + this.get('quux.firstObject.test');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('foo.{bar,baz}', 'quux.[]', 'quux.firstObject.test', function() {\n          return this.get('foo.bar') + this.get('foo.baz') + this.get('quux.firstObject.test');\n        });\n      `,\n      errors: [\n        {\n          message:\n            'Use of undeclared dependencies in computed property: foo.baz, quux.firstObject.test',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // TODO: this should actually be a valid test case because the property added by the fixer (`quux.firstObject.myProp`) is redundant.\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed('quux.@each.myProp', function() {\n          return this.get('quux.firstObject.myProp');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('quux.@each.myProp', 'quux.firstObject.myProp', function() {\n          return this.get('quux.firstObject.myProp');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: quux.firstObject.myProp',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Gracefully handles nesting/array inside braces:\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed('article.{comments.[],title,first.second}', function() {\n          return this.article.title + this.missingProp + someFunction(this.article.comments) + this.article.first.second;\n        });\n      `,\n      // TODO: an improvement would be to use braces here: 'article.{comments.[],first.second,title}'\n      output: `\n        import Ember from 'ember';\n        Ember.computed('article.comments.[]', 'article.first.second', 'article.title', 'missingProp', function() {\n          return this.article.title + this.missingProp + someFunction(this.article.comments) + this.article.first.second;\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: missingProp',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // don't expand @each.{foo,bar}\n      code: `\n        import Ember from 'ember';\n        Ember.computed('foo.@each.{bar,baz}', function() {\n          return this.get('foo').mapBy('bar') + this.get('quux');\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('foo.@each.{bar,baz}', 'quux', function() {\n          return this.get('foo').mapBy('bar') + this.get('quux');\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: quux',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Catch missing injected service name with `requireServiceNames` enabled:\n      code: `\n        import Ember from 'ember';\n        import Component from '@ember/component';\n        import { inject as service } from '@ember/service';\n        Component.extend({\n          intl: service(),\n          myProperty: Ember.computed('foo', function() {\n            console.log(this.intl);\n            return this.get('foo') + this.intl.t('some.translation.key');\n          })\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        import Component from '@ember/component';\n        import { inject as service } from '@ember/service';\n        Component.extend({\n          intl: service(),\n          myProperty: Ember.computed('foo', 'intl', function() {\n            console.log(this.intl);\n            return this.get('foo') + this.intl.t('some.translation.key');\n          })\n        });\n      `,\n      options: [{ requireServiceNames: true }],\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: intl',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Should ignore the left side of an assignment but not the right side.\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          this.left = this.right;\n        })\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('right', function() {\n          this.left = this.right;\n        })\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: right',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Should not ignore properties inside injected service:\n      code: `\n        import Ember from 'ember';\n        import Component from '@ember/component';\n        import { inject as service } from '@ember/service';\n        Component.extend({\n          intl: service(),\n          myProperty: Ember.computed(function() {\n            return this.intl.someProperty;\n          })\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        import Component from '@ember/component';\n        import { inject as service } from '@ember/service';\n        Component.extend({\n          intl: service(),\n          myProperty: Ember.computed('intl.someProperty', function() {\n            return this.intl.someProperty;\n          })\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: intl.someProperty',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: `\n        import Ember from 'ember';\n        Ember.computed(function() {\n          return this.some.very.long.\n            multi.line.property.name;\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('some.very.long.multi.line.property.name', function() {\n          return this.some.very.long.\n            multi.line.property.name;\n        });\n      `,\n      errors: [\n        {\n          message:\n            'Use of undeclared dependencies in computed property: some.very.long.multi.line.property.name',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // String concatenation in dependent key:\n      code: `\n        import Ember from 'ember';\n        Ember.computed('na' + 'me', function() {\n          return this.undeclared + this.name;\n        });\n      `,\n      output: `\n        import Ember from 'ember';\n        Ember.computed('name', 'undeclared', function() {\n          return this.undeclared + this.name;\n        });\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Explicit getter function:\n      code: `\n        import { computed } from '@ember/object';\n        computed('firstName', {\n          get() {\n            return this.firstName + ' ' + this.lastName;\n          },\n          set(key, value) {}\n        })\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        computed('firstName', 'lastName', {\n          get() {\n            return this.firstName + ' ' + this.lastName;\n          },\n          set(key, value) {}\n        })\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: lastName',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Decorator with getter inside object parameter:\n    {\n      code: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('firstName', {\n            get() {\n              return this.firstName + ' ' + this.lastName;\n            },\n            set(key, value) {}\n          })\n          fullName\n        }\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('firstName', 'lastName', {\n            get() {\n              return this.firstName + ' ' + this.lastName;\n            },\n            set(key, value) {}\n          })\n          fullName\n        }\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: lastName',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Decorator with no parens:\n    {\n      code: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed\n          get someProp() { return this.undeclared; }\n        }\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('undeclared')\n          get someProp() { return this.undeclared; }\n        }\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'Identifier',\n        },\n      ],\n    },\n    // Decorator with no args:\n    {\n      code: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed()\n          get someProp() { return this.undeclared; }\n        }\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('undeclared')\n          get someProp() { return this.undeclared; }\n        }\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Decorator with arg:\n    {\n      code: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('first')\n          get fullName() { return this.first + ' ' + this.last; }\n        }\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('first', 'last')\n          get fullName() { return this.first + ' ' + this.last; }\n        }\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: last',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    // Decorator with two arg:\n    {\n      code: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('first', 'last')\n          get fullName() { return this.first + ' ' + this.last + ' ' + this.undeclared; }\n        }\n      `,\n      output: `\n        import { computed } from '@ember/object';\n        class Test {\n          @computed('first', 'last', 'undeclared')\n          get fullName() { return this.first + ' ' + this.last + ' ' + this.undeclared; }\n        }\n      `,\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: undeclared',\n          type: 'CallExpression',\n        },\n      ],\n    },\n\n    {\n      // Optional chaining:\n      code: \"import { computed } from '@ember/object'; computed(function() { return this.x?.y?.z; })\",\n      output:\n        \"import { computed } from '@ember/object'; computed('x.y.z', function() { return this.x?.y?.z; })\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: x.y.z',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Optional chaining plus overlap with non-optional-chaining:\n      code: \"import { computed } from '@ember/object'; computed(function() { return this.x?.y?.z + this.x.y.foo; })\",\n      output:\n        \"import { computed } from '@ember/object'; computed('x.y.{foo,z}', function() { return this.x?.y?.z + this.x.y.foo; })\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: x.y.foo, x.y.z',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Optional chaining with function call:\n      code: \"import { computed } from '@ember/object'; computed(function() { return this.x?.y?.someFunction(); })\",\n      output:\n        \"import { computed } from '@ember/object'; computed('x.y', function() { return this.x?.y?.someFunction(); })\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: x.y',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Optional chaining with array/object access:\n      code: \"import { computed } from '@ember/object'; computed(function() { return this.x?.someArrayOrObject[index]; })\",\n      output:\n        \"import { computed } from '@ember/object'; computed('x.someArrayOrObject', function() { return this.x?.someArrayOrObject[index]; })\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: x.someArrayOrObject',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Renamed Ember import:\n      code: \"import E from 'ember'; E.computed(function() { return this.foo; });\",\n      output: \"import E from 'ember'; E.computed('foo', function() { return this.foo; });\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Renamed computed import:\n      code: \"import { computed as c } from '@ember/object'; c(function() { return this.foo; });\",\n      output:\n        \"import { computed as c } from '@ember/object'; c('foo', function() { return this.foo; });\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Renamed get import:\n      code: \"import { computed, get as g } from '@ember/object'; computed(function() { return g(this, 'foo'); });\",\n      output:\n        \"import { computed, get as g } from '@ember/object'; computed('foo', function() { return g(this, 'foo'); });\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Renamed getProperties import:\n      code: \"import { computed, getProperties as gp } from '@ember/object'; computed(function() { return gp(this, 'foo'); });\",\n      output:\n        \"import { computed, getProperties as gp } from '@ember/object'; computed('foo', function() { return gp(this, 'foo'); });\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo',\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      // Renamed getWithDefault import:\n      code: \"import { computed, getWithDefault as gwd } from '@ember/object'; computed(function() { return gwd(this, 'foo', 'bar'); });\",\n      output:\n        \"import { computed, getWithDefault as gwd } from '@ember/object'; computed('foo', function() { return gwd(this, 'foo', 'bar'); });\",\n      errors: [\n        {\n          message: 'Use of undeclared dependencies in computed property: foo',\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/require-fetch-import.js",
    "content": "'use strict';\n\nconst rule = require('../../../lib/rules/require-fetch-import');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\nconst ruleTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  env: { browser: true },\n});\n\nruleTester.run('require-fetch-import', rule, {\n  valid: [\n    `\n      import fetch from 'fetch';\n\n      fetch('/something');\n    `,\n    `\n      fetch('/something');\n\n      import fetch from 'fetch';\n    `,\n    `\n      import { default as fetch } from 'fetch';\n\n      fetch('/something');\n    `,\n    \"foo('/something');\",\n  ],\n\n  invalid: [\n    {\n      code: \"fetch('/something');\",\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression', line: 1, column: 1 }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/require-return-from-computed.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/require-return-from-computed');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\nconst { ERROR_MESSAGE } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\neslintTester.run('require-return-from-computed', rule, {\n  valid: [\n    'let foo = computed(\"test\", function() { return \"\"; })',\n    'let foo = computed(\"test\", { get() { return true; }, set() { return true; } })',\n    'let foo = computed(\"test\", function() { if (true) { return \"\"; } return \"\"; })',\n    'let foo = computed(\"test\", { get() { data.forEach(function() { }); return true; }, set() { return true; } })',\n    'let foo = computed(\"test\", function() { data.forEach(function() { }); return \"\"; })',\n    '(function() { computed(); } )',\n    {\n      // This rule intentionally does not apply to native classes / decorator usage.\n      // ESLint already has its own recommended rules `getter-return` and `no-setter-return` for this.\n      code: 'class Test { @computed() get someProp() {} set someProp(val) {} }',\n      parser: require.resolve('@babel/eslint-parser'),\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n        ecmaFeatures: { legacyDecorators: true },\n      },\n    },\n  ].map(addComputedImport),\n  invalid: [\n    {\n      code: 'let foo = computed(\"test\", function() { })',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n      ],\n    },\n    {\n      code: \"import Ember from 'ember'; let foo = Ember.computed('test', function() { })\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n      ],\n    },\n    {\n      code: 'let foo = computed(\"test\", function() { if (true) { return \"\"; } })',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n      ],\n    },\n    {\n      code: 'let foo = computed(\"test\", { get() {}, set() {} })',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n      ],\n    },\n    {\n      code: 'let foo = computed({ get() { return \"foo\"; }, set() { }})',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n      ],\n    },\n    {\n      code: 'let foo = computed({ get() { }, set() { return \"foo\"; }})',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'FunctionExpression',\n        },\n      ],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/require-super-in-lifecycle-hooks.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/require-super-in-lifecycle-hooks');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE: message } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  parser: require.resolve('@babel/eslint-parser'),\n});\n\neslintTester.run('require-super-in-lifecycle-hooks', rule, {\n  valid: [\n    `export default Component.extend({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Component.extend({\n        didInsertElement() {\n          return this._super(...arguments);\n        }\n      });`,\n    {\n      code: `export default Component.extend({\n        didInsertElement() {\n          return this._super(...arguments);\n        }\n      });`,\n      options: [{ checkInitOnly: true }],\n    },\n    `export default Route.extend({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Controller.extend({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Mixin.extend({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Service.extend({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Component({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Route({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Controller({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Mixin({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    `export default Service({\n        init() {\n          return this._super(...arguments);\n        }\n      });`,\n    'export default Component.extend();',\n    'export default Route.extend();',\n    'export default Controller.extend();',\n    'export default Mixin.extend();',\n    'export default Service.extend();',\n    {\n      code: 'export default Service.extend({ ...spread })',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    `export default Component({\n        init() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Route({\n        init() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Controller({\n        init() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Mixin({\n        init() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Service({\n        init() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Component({\n        init: function() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Route({\n        init: function() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Controller({\n        init: function() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Mixin({\n        init: function() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Service({\n        init: function() {\n          this._super(...arguments);\n        },\n      });`,\n    `export default Service({\n        init\n      });`,\n\n    // Properly-used glimmer hook:\n    \"import Component from '@glimmer/component'; class Foo extends Component { willDestroy() { super.willDestroy(); }}\",\n\n    // Non-init hooks should not be checked when checkInitOnly = true\n    {\n      code: 'export default Component({ didInsertElement() {} });',\n      options: [{ checkInitOnly: true }],\n    },\n    {\n      code: \"import Component from '@ember/component'; class Foo extends Component { didInsertElement() {} }\",\n      options: [{ checkNativeClasses: true, checkInitOnly: true }],\n    },\n    {\n      code: \"import Component from '@glimmer/component'; class Foo extends Component { willDestroy() {} }\",\n      options: [{ checkInitOnly: true }],\n    },\n\n    // Native classes should not be checked when checkNativeClasses = false\n    {\n      code: `import Component from '@ember/component';\n     class Foo extends Component { init() { } }`,\n      options: [{ checkNativeClasses: false }],\n    },\n\n    // checkNativeClasses = true\n    {\n      code: `import Service from '@ember/service';\n     class Foo extends Service { init() { super.init(...arguments); }}`,\n      options: [{ checkNativeClasses: true }],\n    },\n    {\n      code: `import Component from '@ember/component';\n     class Foo extends Component { didInsertElement() { super.didInsertElement(...arguments); }}`,\n      options: [{ checkNativeClasses: true, checkInitOnly: false }],\n    },\n    {\n      code: `import Service from '@ember/service';\n     class Foo extends Service { didInsertElement() { }}`,\n      options: [{ checkNativeClasses: true, checkInitOnly: false }],\n    },\n\n    // checkInitOnly = false\n    {\n      code: 'export default Component({ didInsertElement() { this._super();} });',\n      options: [{ checkInitOnly: false }],\n    },\n\n    // Not inside Ember class:\n    {\n      code: 'export default Foo({ init() { } });',\n      options: [{ checkInitOnly: false }],\n    },\n    {\n      code: 'class Foo { init() { } }',\n      options: [{ checkNativeClasses: true }],\n    },\n    'export default Foo({ didInsertElement() { } });',\n    {\n      code: 'class Foo { didInsertElement() { } }',\n      options: [{ checkNativeClasses: true, checkInitOnly: false }],\n    },\n    'const foo = { init() { } }',\n\n    // init hook should be checked in all Ember classes:\n    {\n      code: \"import Controller from '@ember/controller'; class Foo extends Controller { init() { super.init(...arguments); }}\",\n      options: [{ checkNativeClasses: true }],\n    },\n    {\n      code: \"import Route from '@ember/routing/route'; class Foo extends Route { init() { super.init(...arguments); }}\",\n      options: [{ checkNativeClasses: true }],\n    },\n    {\n      code: \"import Service from '@ember/service'; class Foo extends Service { init() { super.init(...arguments); }}\",\n      options: [{ checkNativeClasses: true }],\n    },\n    {\n      code: \"import Mixin from '@ember/object/mixin'; class Foo extends Mixin { init() { super.init(...arguments); }}\",\n      options: [{ checkNativeClasses: true }],\n    },\n\n    // Glimmer component allows non-glimmer hook to be used without super:\n    \"import Component from '@glimmer/component'; class Foo extends Component { init() {} }\",\n    \"import Component from '@glimmer/component'; class Foo extends Component { didInsertElement() {} }\",\n\n    // Does not throw with node type (ClassProperty) not handled by estraverse.\n    'Component.extend({init() { class Foo { @someDecorator() someProp }; return this._super(...arguments); } });',\n  ],\n  invalid: [\n    {\n      code: `export default Component.extend({\n        init() {},\n      });`,\n      output: `export default Component.extend({\n        init() {\nthis._super(...arguments);},\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component.extend({\n        didInsertElement() {},\n      });`,\n      output: `export default Component.extend({\n        didInsertElement() {\nthis._super(...arguments);},\n      });`,\n      options: [{ checkInitOnly: false }],\n      errors: [{ message, line: 2 }],\n    },\n\n    // checkInitOnly = false\n    {\n      code: 'export default Component.extend({ didInsertElement() {} });',\n      output: `export default Component.extend({ didInsertElement() {\nthis._super(...arguments);} });`,\n      errors: [{ message, type: 'Property' }],\n    },\n    {\n      code: `export default Component.extend({\n        init() {},\n      });`,\n      output: `export default Component.extend({\n        init() {\nthis._super(...arguments);},\n      });`,\n      options: [{ checkInitOnly: false }],\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: 'import Component from \"@glimmer/component\"; class Foo extends Component { willDestroy() {} }',\n      output: `import Component from \"@glimmer/component\"; class Foo extends Component { willDestroy() {\nsuper.willDestroy(...arguments);} }`,\n      errors: [{ message, type: 'MethodDefinition' }],\n    },\n\n    {\n      code: `export default Component.extend({\n        init() {\n          this.set('prop', 'value');\n        },\n      });`,\n      output: `export default Component.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component.extend({\n        init() {\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      output: `export default Component.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route.extend({\n        init() {\n\n        },\n      });`,\n      output: `export default Route.extend({\n        init() {\nthis._super(...arguments);\n\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route.extend({\n        init() {\n          // Foo\n          console.log();\n        },\n      });`,\n      output: `export default Route.extend({\n        init() {\nthis._super(...arguments);\n          // Foo\n          console.log();\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route.extend({\n        init() {\n          this.set('prop', 'value');\n        },\n      });`,\n      output: `export default Route.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route.extend({\n        init() {\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      output: `export default Route.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller.extend({\n        init() {},\n      });`,\n      output: `export default Controller.extend({\n        init() {\nthis._super(...arguments);},\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller.extend({\n        init() {\n          this.set('prop', 'value');\n        },\n      });`,\n      output: `export default Controller.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller.extend({\n        init() {\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      output: `export default Controller.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin.extend({\n        init() {},\n      });`,\n      output: `export default Mixin.extend({\n        init() {\nthis._super(...arguments);},\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin.extend({\n        init() {\n          this.set('prop', 'value');\n        },\n      });`,\n      output: `export default Mixin.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin.extend({\n        init() {\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      output: `export default Mixin.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service.extend({\n        init() {},\n      });`,\n      output: `export default Service.extend({\n        init() {\nthis._super(...arguments);},\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service.extend({\n        init() {\n          this.set('prop', 'value');\n        },\n      });`,\n      output: `export default Service.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service.extend({\n        init() {\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      output: `export default Service.extend({\n        init() {\nthis._super(...arguments);\n          this.set('prop', 'value');\n          this.set('prop2', 'value2');\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component.extend({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Component.extend({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route.extend({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Route.extend({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller.extend({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Controller.extend({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin.extend({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Mixin.extend({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service.extend({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Service.extend({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Component({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Route({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Controller({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Mixin({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service({\n        init() {\n          return;\n        }\n      });`,\n      output: `export default Service({\n        init() {\nthis._super(...arguments);\n          return;\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component.extend({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Component.extend({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route.extend({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Route.extend({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller.extend({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Controller.extend({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin.extend({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Mixin.extend({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service.extend({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Service.extend({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Component({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Component({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Route({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Route({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Controller({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Controller({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Mixin({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Mixin({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service({\n        init() {\n          return 'meh';\n        }\n      });`,\n      output: `export default Service({\n        init() {\nthis._super(...arguments);\n          return 'meh';\n        }\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n    {\n      code: `export default Service({\n        init() {\n          someRandomIdentifier;\n        },\n      });`,\n      output: `export default Service({\n        init() {\nthis._super(...arguments);\n          someRandomIdentifier;\n        },\n      });`,\n      errors: [{ message, line: 2 }],\n    },\n\n    // attrs hooks should call super without ...arguments to satisfy ember/no-attrs-snapshot rule.\n    {\n      code: 'Component({ didReceiveAttrs() {} })',\n      output: `Component({ didReceiveAttrs() {\nthis._super();} })`,\n      options: [{ checkInitOnly: false }],\n      errors: [{ message, line: 1 }],\n    },\n\n    // checkInitOnly = false\n    {\n      code: \"import Component from '@ember/component'; class Foo extends Component { didUpdateAttrs() {} }\",\n      output: `import Component from '@ember/component'; class Foo extends Component { didUpdateAttrs() {\nsuper.didUpdateAttrs();} }`,\n      options: [{ checkNativeClasses: true }],\n      errors: [{ message, type: 'MethodDefinition' }],\n    },\n    {\n      code: `import Component from '@ember/component';\n      class Foo extends Component { didUpdateAttrs() {} }`,\n      output: `import Component from '@ember/component';\n      class Foo extends Component { didUpdateAttrs() {\nsuper.didUpdateAttrs();} }`,\n      options: [{ checkNativeClasses: true, checkInitOnly: false }],\n      errors: [{ message, line: 2 }],\n    },\n\n    // Native classes:\n    {\n      code: \"import Service from '@ember/service'; class Foo extends Service { init() {} }\",\n      output: `import Service from '@ember/service'; class Foo extends Service { init() {\nsuper.init(...arguments);} }`,\n      errors: [{ message, type: 'MethodDefinition' }],\n    },\n    {\n      code: `import Service from '@ember/service';\n      class Foo extends Service {\n        init() {}\n      }`,\n      output: `import Service from '@ember/service';\n      class Foo extends Service {\n        init() {\nsuper.init(...arguments);}\n      }`,\n      options: [{ checkNativeClasses: true }],\n      errors: [{ message, line: 3 }],\n    },\n    {\n      code: `import Component from '@ember/component';\n      class Foo extends Component {\n        didInsertElement() {}\n      }`,\n      output: `import Component from '@ember/component';\n      class Foo extends Component {\n        didInsertElement() {\nsuper.didInsertElement(...arguments);}\n      }`,\n      options: [{ checkNativeClasses: true, checkInitOnly: false }],\n      errors: [{ message, line: 3 }],\n    },\n    {\n      code: `import Mixin from '@ember/object/mixin';\n      class Foo extends Mixin {\n        didInsertElement() {}\n      }`,\n      output: `import Mixin from '@ember/object/mixin';\n      class Foo extends Mixin {\n        didInsertElement() {\nsuper.didInsertElement(...arguments);}\n      }`,\n      options: [{ checkNativeClasses: true, checkInitOnly: false }],\n      errors: [{ message, line: 3 }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/require-tagless-components.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/require-tagless-components');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE_REQUIRE_TAGLESS_COMPONENTS: ERROR_MESSAGE } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: {\n      legacyDecorators: true,\n    },\n  },\n});\n\nruleTester.run('require-tagless-components', rule, {\n  valid: [\n    `\n      import Component from '@ember/component';\n      export default Component.extend({ tagName: '' });\n    `,\n    `\n      import Component from '@ember/component';\n      export default Component.extend(Mixin, { tagName: '' });\n    `,\n    `\n      import Component from '@ember/component';\n      export default class MyComponent extends Component {\n        tagName = ''\n      }\n    `,\n    `\n      import Component from '@ember/component';\n      export default class MyComponent extends Component.extend(Mixin) {\n        tagName = ''\n      }\n    `,\n    `\n      import Component from '@ember/component';\n      import { tagName } from '@ember-decorators/component';\n      @tagName('')\n      export default class MyComponent extends Component {}\n    `,\n    `\n      import Component from '@glimmer/component';\n      export default class MyComponent extends Component {}\n    `,\n    `\n      import SomeOtherThing from 'some-other-module';\n      export default SomeOtherThing.extend({\n        tagName: 'some-non-empty-value'\n      });\n    `,\n    `\n      import SomeOtherThing from 'some-other-module';\n      export default class MyThing extends SomeOtherThing {\n        tagName = 'some-non-empty-value';\n      }\n    `,\n    {\n      // Classic service in component file.\n      filename: 'app/components/foo.js',\n      code: `\n        import Service from '@ember/service';\n        export default Service.extend({});\n      `,\n    },\n    {\n      // Native service in component file.\n      filename: 'app/components/foo.js',\n      code: `\n        import Service from '@ember/service';\n        export default class MyService extends Service {};\n      `,\n    },\n    {\n      // Should ignore test files.\n      filename: 'tests/integration/components/foo-test.js',\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {};\n        Component.extend({});\n      `,\n    },\n  ],\n  invalid: [\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend({});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend();\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend(SomeMixin, {});\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'CallExpression' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend({\n          tagName: 'span'\n        });\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, type: 'Property' }],\n    },\n    {\n      // `tagName` but not the last object argument\n      code: `\n        import Component from '@ember/component';\n        export default Component.extend({ tagName: 'span' }, SomeMixin);\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'Property' }],\n    },\n    {\n      // `tagName` but inside a variable\n      code: `\n        import Component from '@ember/component';\n        const body = { tagName: 'span' };\n        export default Component.extend(body);\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'Property' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {}\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 3, type: 'ClassBody' }],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        export default class MyComponent extends Component {\n          tagName = 'span'\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          line: 4,\n          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)\n        },\n      ],\n    },\n    {\n      code: `\n        import Component from '@ember/component';\n        import { tagName } from '@ember-decorators/component';\n        @tagName('span')\n        export default class MyComponent extends Component {}\n      `,\n      output: null,\n      errors: [{ message: ERROR_MESSAGE, line: 4, type: 'Decorator' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/require-valid-css-selector-in-test-helpers.js",
    "content": "const rule = require('../../../lib/rules/require-valid-css-selector-in-test-helpers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nconst EXAMPLE_SELECTORS = [\n  '.foo',\n  '.bar',\n  '  .with-spaces-around-this-one  ',\n  '#userlist',\n  'p',\n  '[data-test-foo] svg',\n  'div.highlighted > p',\n  'iframe[data-src]',\n  'li[data-active=\"1\"]',\n];\n\nruleTester.run('require-valid-css-selector-in-test-helpers', rule, {\n  valid: [\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { find } from '@ember/test-helpers';\n        find('[data-test-pizza]')`,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { find } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            find('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            fooBar.dom('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n      import { find } from '@ember/test-helpers';\n      import { module } from 'qunit';\n\n      module('foo', function() {});\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n      import { find as module } from '@ember/test-helpers';\n      module('[data-test-foo]');\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        const assert = { dom() {} };\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            assert.dom('.classname');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            this.element.querySelector('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            document.querySelector('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            this.element.querySelectorAll('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            document.querySelectorAll('[data-test-pizza]');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            document.querySelectorAll('#foobar');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            document.querySelectorAll('.foobar');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            document.querySelectorAll('div');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            document.querySelectorAll('.foo\\\\+bar');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        this.click('.foobar');\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { fillIn } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            fillIn('.foobar', 'Jeff Sturgis');\n          });\n        });\n      `,\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { fillIn, visit } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            visit('/foobar');\n            fillIn('.foobar', 'Jeff Sturgis');\n          });\n        });\n      `,\n    },\n\n    // Ignored because not a test file.\n    {\n      filename: 'components/foobar.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            this.element.querySelectorAll('[data-test-foo-bar');\n          });\n        });\n      `,\n    },\n\n    // With comma(s) separating multiple selectors:\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('${EXAMPLE_SELECTORS.join(',')}');\n          });\n        });\n      `,\n    },\n\n    // With comma(s) inside quotes in a selector:\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('[data-test-row=\"London, England, GB\"], .foo').exists();\n          });\n        });\n      `,\n    },\n  ],\n  invalid: [\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('[data-test-pizza');\n          });\n        });\n        `,\n      output: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('[data-test-pizza]');\n          });\n        });\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { find, click, fillIn, findAll, focus, blur, doubleClick, scrollTo, select, tap, triggerEvent, triggerKeyEvent, typeIn, waitFor } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            find('[data-test-pizza');\n            findAll('[data-test-pizza');\n            fillIn('[data-test-pizza');\n            click('[data-test-pizza');\n            focus('[data-test-pizza');\n            blur('[data-test-pizza');\n            doubleClick('[data-test-pizza');\n            scrollTo('[data-test-pizza');\n            select('[data-test-pizza');\n            tap('[data-test-pizza');\n            triggerEvent('[data-test-pizza');\n            triggerKeyEvent('[data-test-pizza');\n            typeIn('[data-test-pizza');\n            waitFor('[data-test-pizza');\n          });\n        });\n        `,\n      output: `\n        import { find, click, fillIn, findAll, focus, blur, doubleClick, scrollTo, select, tap, triggerEvent, triggerKeyEvent, typeIn, waitFor } from '@ember/test-helpers';\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            find('[data-test-pizza]');\n            findAll('[data-test-pizza]');\n            fillIn('[data-test-pizza]');\n            click('[data-test-pizza]');\n            focus('[data-test-pizza]');\n            blur('[data-test-pizza]');\n            doubleClick('[data-test-pizza]');\n            scrollTo('[data-test-pizza]');\n            select('[data-test-pizza]');\n            tap('[data-test-pizza]');\n            triggerEvent('[data-test-pizza]');\n            triggerKeyEvent('[data-test-pizza]');\n            typeIn('[data-test-pizza]');\n            waitFor('[data-test-pizza]');\n          });\n        });\n        `,\n      errors: Array.from({ length: 14 }).fill({\n        type: 'CallExpression',\n        messageId: 'unclosedAttr',\n      }),\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { find } from '@ember/test-helpers';\n        find('[data-test-pizza');`,\n      output: `\n        import { find } from '@ember/test-helpers';\n        find('[data-test-pizza]');`,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            this.element.querySelector('[data-test-pizza');\n          });\n        });\n        `,\n      output: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            this.element.querySelector('[data-test-pizza]');\n          });\n        });\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            document.querySelector('[data-test-pizza');\n          });\n        });\n        `,\n      output: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            document.querySelector('[data-test-pizza]');\n          });\n        });\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            this.element.querySelectorAll('[data-test-pizza');\n          });\n        });\n        `,\n      output: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            this.element.querySelectorAll('[data-test-pizza]');\n          });\n        });\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            document.querySelectorAll('[data-test-pizza');\n          });\n        });\n        `,\n      output: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            document.querySelectorAll('[data-test-pizza]');\n          });\n        });\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            document.querySelectorAll('#1234');\n          });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'idStartsWithNumber',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        test('foo', function(fooBar) {\n          document.querySelectorAll('..foobar');\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(fooBar) {\n            document.querySelectorAll('##foobar');\n          });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { find as findRenamed } from '@ember/test-helpers';\n\n        findRenamed('[data-foo')\n        `,\n      output: `\n        import { find as findRenamed } from '@ember/test-helpers';\n\n        findRenamed('[data-foo]')\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test as testRenamed } from 'qunit';\n\n        module('foo', function() {\n          testRenamed('foo', function() {\n            document.querySelectorAll('[data-foo')\n          });\n        });\n        `,\n      output: `\n        import { module, test as testRenamed } from 'qunit';\n\n        module('foo', function() {\n          testRenamed('foo', function() {\n            document.querySelectorAll('[data-foo]')\n          });\n        });\n        `,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'unclosedAttr',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function() {\n            document.querySelectorAll('data-test]')\n          });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { expect } from 'chai';\n        import { describe, it } from 'mocha';\n        import { setupTest } from 'ember-mocha';\n\n        describe('foo', function() {\n          setupTest();\n\n          beforeEach(function() {\n            this.elem = this.element.querySelectorAll('data-test]');\n          });\n\n          it('exists', function() {\n            expect(this.elem).to.be.ok;\n          });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { expect } from 'chai';\n        import { describe, it } from 'mocha';\n        import { setupTest } from 'ember-mocha';\n\n        describe('foo', function() {\n          setupTest();\n\n          afterEach(function() {\n            this.elem = this.element.querySelectorAll('data-test]');\n          });\n\n          it('exists', function() {\n            expect(this.elem).to.be.ok;\n          });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('Acceptance | foo', function (hooks) {\n          hooks.beforeEach(function () {\n            this.elem = this.element.querySelectorAll('data-test]');\n          });\n            test('foo', function(assert) {\n              assert.ok(this.elem);\n            });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('Acceptance | foo', function (hooks) {\n          hooks.afterEach(function () {\n            this.elem = this.element.querySelectorAll('data-test]');\n          });\n            test('foo', function(assert) {\n              assert.ok(this.elem);\n            });\n        });\n        `,\n      output: null,\n      errors: [\n        {\n          type: 'CallExpression',\n          messageId: 'other',\n        },\n      ],\n    },\n\n    // With comma(s) separating multiple selectors:\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('.foo, 5bar');\n          });\n        });\n        `,\n      output: null,\n      errors: [{ type: 'CallExpression', messageId: 'other' }],\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('.foo, #1234');\n          });\n        });\n        `,\n      output: null,\n      errors: [{ type: 'CallExpression', messageId: 'idStartsWithNumber' }],\n    },\n    {\n      filename: 'components/foobar-test.js',\n      code: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('.foo, [data-test-row=\"London, England, GB\", .bar');\n          });\n        });\n        `,\n      output: `\n        import { module, test } from 'qunit';\n\n        module('foo', function() {\n          test('foo', function(assert) {\n            assert.dom('.foo, [data-test-row=\"London, England, GB\"], .bar');\n          });\n        });\n        `,\n      errors: [{ type: 'CallExpression', messageId: 'unclosedAttr' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/route-path-style.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/route-path-style');\nconst RuleTester = require('eslint').RuleTester;\n\nconst { ERROR_MESSAGE } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n  parser: require.resolve('@babel/eslint-parser'),\n});\nruleTester.run('route-path-style', rule, {\n  valid: [\n    // Implicit path:\n    'this.route(\"blog\");',\n    'this.route(\"blog-posts\");',\n\n    // Explicit path:\n    'this.route(\"blog\", { path: \"\" });',\n    'this.route(\"blog\", { path: \"/\" });',\n    'this.route(\"blog\", { path: \"*:\" });',\n    'this.route(\"blog\", { path: \"/blog\" });',\n    'this.route(\"blog\", { path: \"/blog/blog\" });',\n    'this.route(\"blog\", { path: \"/blog/blog-posts\" });',\n    'this.route(\"blog-posts\", { path: \"/blog-posts\" });',\n    'this.route(\"blog_posts\", { path: \"/blog-posts\" });',\n    'this.route(\"blogPosts\", { path: \"/blog-posts\" });',\n\n    // With dynamic segments:\n    'this.route(\"blog\", { path: \"/blog/blog-posts/:blog_id\" });',\n    'this.route(\"blog\", { path: \":blog_id\" });',\n    'this.route(\"blog\", { path: \"blog/:blog_id\" });',\n    'this.route(\"blog\", { path: \"/blog/:blog_id\" });',\n    'this.route(\"blog\", { path: \":blog_id/:other_id\" });',\n    'this.route(\"blog\", { path: \"/blog/:blog_id/test/:other_id\" });',\n\n    // With wildcard segment:\n    'this.route(\"blog\", { path: \"*path\" });',\n    'this.route(\"blog\", { path: \"*path_name\" });',\n    'this.route(\"blog\", { path: \"*pathName\" });',\n    'this.route(\"blog\", { path: \"/*path\" });',\n    'this.route(\"blog\", { path: \"/*path_name\" });',\n    'this.route(\"blog\", { path: \"/*pathName\" });',\n    'this.route(\"blog\", { path: \"/blog/*path\" });',\n    'this.route(\"blog\", { path: \"/blog/*path_name\" });',\n    'this.route(\"blog\", { path: \"/blog/*pathName\" });',\n\n    // With function:\n    'this.route(\"blog\", function() { this.route(\"update\"); });',\n    'this.route(\"blog\", { path: \"/blog-posts\" }, function() { this.route(\"update\"); });',\n\n    // With other field in object:\n    'this.route(\"blog\", { otherField: \"/blog_posts\" });',\n    'this.route(\"blog\", { otherField: \"/blog_posts\", path: \"/blog\" });',\n    'this.route(\"blog-posts\", { otherField: \"/blog_posts\" });',\n    'this.route(\"blog-posts\", { ...foo });',\n\n    // Not Ember's route function:\n    'test();',\n    \"test('blog');\",\n    \"test('blog_posts');\",\n    \"test('blogPosts');\",\n    \"test('blog-posts', { path: '/blog_posts' });\",\n    'this.test();',\n    \"this.test('blog');\",\n    \"this.test('blog_posts');\",\n    \"this.test('blogPosts');\",\n    \"this.test('blog-posts', { path: '/blog_posts' });\",\n    'MyClass.route();',\n    \"MyClass.route('blog');\",\n    \"MyClass.route('blog_posts');\",\n    \"MyClass.route('blogPosts');\",\n    \"MyClass.route('blog-posts', { path: '/blog_posts' });\",\n    \"route.unrelatedFunction('blog_posts');\",\n    \"this.route.unrelatedFunction('blog_posts');\",\n\n    // Incorrect usage:\n    'this.route();',\n\n    // With variable:\n    \"this.route('team', { path: myPath });\",\n    \"this.route('team', { path: `part-${some-variable}` });\", // eslint-disable-line no-template-curly-in-string\n  ],\n  invalid: [\n    {\n      code: 'this.route(\"blog_posts\");',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [{ messageId: 'convertToKebabCase', output: 'this.route(\"blog-posts\");' }],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blogPosts\");',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [{ messageId: 'convertToKebabCase', output: 'this.route(\"blog-posts\");' }],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blogPosts\", function() {});',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            { messageId: 'convertToKebabCase', output: 'this.route(\"blog-posts\", function() {});' },\n          ],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blog-posts\", { path: \"/blog_posts\" });',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'this.route(\"blog-posts\", { path: \"/blog-posts\" });',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blog-posts\", { path: \"/blogPosts\" });',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'this.route(\"blog-posts\", { path: \"/blog-posts\" });',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      // With object variable.\n      code: 'const options = { path: \"/blogPosts\" }; this.route(\"blog-posts\", options);',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'const options = { path: \"/blog-posts\" }; this.route(\"blog-posts\", options);',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blog-posts\", { path: \"/blogPosts/:blog_id\" });',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'this.route(\"blog-posts\", { path: \"/blog-posts/:blog_id\" });',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blog-posts\", { path: \"/blog-posts/:blog_id/otherPart\" });',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'this.route(\"blog-posts\", { path: \"/blog-posts/:blog_id/other-part\" });',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blog-posts\", { path: \"/blogPosts/*path\" });',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'this.route(\"blog-posts\", { path: \"/blog-posts/*path\" });',\n            },\n          ],\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"blog-posts\", { path: \"/blogPosts\" }, function() {});',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'Literal',\n          suggestions: [\n            {\n              messageId: 'convertToKebabCase',\n              output: 'this.route(\"blog-posts\", { path: \"/blog-posts\" }, function() {});',\n            },\n          ],\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/routes-segments-snake-case.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/routes-segments-snake-case');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({ parser: require.resolve('@babel/eslint-parser') });\neslintTester.run('routes-segments-snake-case', rule, {\n  valid: [\n    'this.route(\"tree\", { path: \":tree_id\"});',\n    'this.route(\"tree\", { path: \"/test/:tree_id\"});',\n    'this.route(\"email\");',\n    'this.route(\"facebook-messages\", { path: \"fb-messages\" });',\n    'this.route(\"summary\", { path: \"/\" });',\n    'this.route(\"reset-password\", function() {this.route(\"update\");});',\n    'this.test()',\n    'this.route(\"tree\", { path: \":tree_id/:tree_child_id\"});',\n    'this.route(\"tree\", { path: \"/test/:tree_id/:tree_child_id\"});',\n    'this.route(\"tree\", { path: \"/test/:tree_id/test/:tree_child_id\"});',\n    'this.route(\"tree\", { path: \"/test/:tree_id/test_test/:tree_child_id\"});',\n    'this.route(\"tree\", { path: \"/test/:tree_id/test-test/:tree_child_id\"});',\n    'this.route(\"tree\", { path: \"/test/:tree_id/testTest/:tree_child_id\"});',\n    'this.route(\"tree\", { path: \"*:\"});',\n    'this.route(\"tree\", { ...foo });',\n  ],\n  invalid: [\n    {\n      code: 'this.route(\"tree\", { path: \":treeId\"});',\n      output: null,\n      errors: [\n        {\n          message: 'Use snake case in dynamic segments of routes',\n        },\n      ],\n    },\n    {\n      // With object variable.\n      code: 'const options = { path: \":treeId\"}; this.route(\"tree\", options);',\n      output: null,\n      errors: [{ message: 'Use snake case in dynamic segments of routes', type: 'Literal' }],\n    },\n    {\n      code: 'this.route(\"tree\", { path: \":tree-id\" });',\n      output: null,\n      errors: [\n        {\n          message: 'Use snake case in dynamic segments of routes',\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"tree\", { path: \"/test/:treeId\"});',\n      output: null,\n      errors: [\n        {\n          message: 'Use snake case in dynamic segments of routes',\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"tree\", { path: \"/test/treeId/:treeChildId\"});',\n      output: null,\n      errors: [\n        {\n          message: 'Use snake case in dynamic segments of routes',\n        },\n      ],\n    },\n    {\n      code: 'this.route(\"tree\", { path: \"/test/tree-id/:tree-child-id\"});',\n      output: null,\n      errors: [\n        {\n          message: 'Use snake case in dynamic segments of routes',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-attribute-indentation.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-attribute-indentation');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\n// ---- HBS tests ----\n\nhbsRuleTester.run('template-attribute-indentation', rule, {\n  valid: [\n    // Short invocations on a single line are fine (< 80 chars)\n    '{{employee-details firstName=firstName}}',\n    '<input disabled>',\n\n    // Non-block with no params\n    '{{contact-details}}',\n    // Default config with open-invocation (< 80 chars) - positional params\n    '{{contact-details firstName lastName}}',\n    // named params\n    '{{contact-details firstName=firstName lastName=lastName}}',\n\n    // Mustache non-block with proper indentation and new-line close\n    {\n      code: ['{{contact-details', '  firstName=firstName', '  lastName=lastName', '}}'].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n    // Mustache non-block with last-attribute close\n    {\n      code: ['{{contact-details', '  firstName=firstName', '  lastName=lastName}}'].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute' }],\n    },\n\n    // Open-invocation with multiple lines\n    ['{{contact-details', '  firstName=firstName', '  lastName=lastName', '}}'].join('\\n'),\n    // Positional params multi-line\n    ['{{contact-details', '  firstName', '  lastName', '}}'].join('\\n'),\n    // Helper\n    [\n      '{{if',\n      '  (or logout.isRunning (not session.isAuthenticated))',\n      '  \"Logging Out...\"',\n      '  \"Log Out\"',\n      '}}',\n    ].join('\\n'),\n    // Helper unfolded\n    [\n      '{{if',\n      '  (or ',\n      '    logout.isRunning',\n      '    (not session.isAuthenticated)',\n      '  )',\n      '  \"Logging Out...\"',\n      '  \"Log Out\"',\n      '}}',\n    ].join('\\n'),\n    // Positional null\n    ['{{contact-null', '  null', '}}'].join('\\n'),\n    // Component helper\n    ['{{component', '  field', '  action=(action reaction)', '}}'].join('\\n'),\n    // Multiple open-invocations\n    [\n      '{{contact-details',\n      '  firstName=firstName',\n      '  lastName=lastName',\n      '}}',\n      '{{contact-details',\n      '  firstName=firstName',\n      '  lastName=lastName',\n      '}}',\n    ].join('\\n'),\n    // Component from hash\n    ['{{t.body', '  canExpand=true', '}}'].join('\\n'),\n    // With helper\n    ['{{print-debug', '  foo=(or', '    foo', '    bar', '  )', '  baz=baz', '}}'].join('\\n'),\n    // With positional helper\n    ['{{print-debug', '  (hash', '    foo=\"bar\"', '  )', '  title=\"baz\"', '}}'].join('\\n'),\n    // yield with hash\n    [\n      '{{yield',\n      '  (hash',\n      '    header=(component \"x-very-long-name-header\")',\n      '    body=(component \"x-very-long-name-body\")',\n      '  )',\n      '}}',\n    ].join('\\n'),\n\n    // Block form within 80 characters - positional params\n    ['{{#contact-details firstName lastName}}', ' {{contactImage}}', '{{/contact-details}}'].join(\n      '\\n'\n    ),\n    // Block form with named params\n    [\n      '{{#contact-details firstName=firstName lastName=lastName}}',\n      ' {{contactImage}}',\n      '{{/contact-details}}',\n    ].join('\\n'),\n    // Component from hash block form\n    [\n      '{{#t.body',\n      '  canExpand=true',\n      '  multiRowExpansion=false',\n      '}}',\n      '  {{foo}}',\n      '{{/t.body}}',\n    ].join('\\n'),\n    // Block form with block params\n    [\n      '{{#contact-details firstName=firstName lastName=lastName as |contact|}}',\n      ' {{contact.fullName}}',\n      '{{/contact-details}}',\n    ].join('\\n'),\n    // Component from positional block form\n    [\n      '{{#t.body',\n      '  canExpand=(helper help)',\n      '  multiRowExpansion=false',\n      'as |body|',\n      '}}',\n      '  {{foo}}',\n      '{{/t.body}}',\n    ].join('\\n'),\n    // Indented block params\n    [\n      '  {{#t.body',\n      '    canExpand=(helper help)',\n      '    multiRowExpansion=false',\n      '  as |body|',\n      '  }}',\n      '    {{foo}}',\n      '  {{/t.body}}',\n    ].join('\\n'),\n\n    // Non-block form with open-invocation-max-len\n    {\n      code: '{{contact-details firstName=firstName lastName=lastName avatarUrl=avatarUrl age=age address=address phoneNo=phoneNo}}',\n      options: [{ 'open-invocation-max-len': 120 }],\n    },\n\n    // Block form with open-invocation > 80, config allows 120\n    {\n      code: [\n        '{{#contact-details firstName=firstName lastName=lastName age=age avatarUrl=avatarUrl as |contact|}}',\n        ' {{contact.fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute', 'open-invocation-max-len': 120 }],\n    },\n\n    // Block form with multiple line invocation\n    [\n      '{{#contact-details',\n      '  firstName=firstName',\n      '  lastName=lastName',\n      'as |fullName|',\n      '}}',\n      '  {{fullName}}',\n      '{{/contact-details}}',\n    ].join('\\n'),\n    // Block form with no params\n    [\n      '{{#contact-details',\n      'as |contact|',\n      '}}',\n      '  {{contact.fullName}}',\n      '{{/contact-details}}',\n    ].join('\\n'),\n\n    // Nested elements sanity check\n    '<div>\\n  <p></p>\\n</div>',\n\n    // Block params with last-attribute\n    {\n      code: [\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute' }],\n    },\n    // Block params with new-line\n    {\n      code: [\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |',\n        '}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n\n    // Mixed element + mustache: new-line/new-line\n    {\n      code: [\n        '<div',\n        '  class=\"classy\"',\n        '>',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |',\n        '}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'new-line' }],\n    },\n    // Mixed element + mustache: last-attribute/last-attribute\n    {\n      code: [\n        '<div',\n        '  class=\"classy\">',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'last-attribute' }],\n    },\n    // Mixed element + mustache: last-attribute/new-line\n    {\n      code: [\n        '<div',\n        '  class=\"classy\"',\n        '>',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'new-line' }],\n    },\n    // Mixed element + mustache: new-line/last-attribute\n    {\n      code: [\n        '<div',\n        '  class=\"classy\">',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |',\n        '}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'last-attribute' }],\n    },\n\n    // Block form with proper indentation\n    {\n      code: [\n        '{{#employee-details',\n        '  firstName=firstName',\n        '  lastName=lastName',\n        'as |employee|',\n        '}}',\n        '  {{employee.fullName}}',\n        '{{/employee-details}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n    // Block form with as-indentation attribute\n    {\n      code: ['{{#foo', '  attribute=this.mine', '  as |let|', '}}', '{{/foo}}'].join('\\n'),\n      options: [\n        {\n          'mustache-open-end': 'new-line',\n          'element-open-end': 'new-line',\n          'as-indentation': 'attribute',\n        },\n      ],\n    },\n    // Block form with as-indentation closing-brace\n    {\n      code: ['{{#foo', '  attribute=this.mine', 'as |let|', '}}', '{{/foo}}'].join('\\n'),\n      options: [\n        {\n          'mustache-open-end': 'new-line',\n          'element-open-end': 'new-line',\n          'as-indentation': 'closing-brace',\n        },\n      ],\n    },\n\n    // Nested sub-expression in element attribute\n    {\n      code: [\n        '<div',\n        '  foo={{action',\n        '    (if',\n        '      abc',\n        '      def',\n        '      ghi)',\n        '    stuff',\n        '  }}',\n        '  baz=qux',\n        '/>',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'new-line' }],\n    },\n\n    // HTML element with new-line close\n    {\n      code: '<div\\n  foo=bar\\n  baz=qux\\n/>',\n      options: [{ 'element-open-end': 'new-line' }],\n    },\n    // HTML element with last-attribute close\n    {\n      code: '<div\\n  foo=bar\\n  baz=qux/>',\n      options: [{ 'element-open-end': 'last-attribute' }],\n    },\n    // Input element with new-line close\n    {\n      code: '<input\\n  foo=bar\\n  baz=qux\\n>',\n      options: [{ 'element-open-end': 'new-line' }],\n    },\n    // Input element with last-attribute close\n    {\n      code: '<input\\n  foo=bar\\n  baz=qux>',\n      options: [{ 'element-open-end': 'last-attribute' }],\n    },\n\n    // Element with mustache-open-end:new-line, element-open-end:last-attribute\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff', '  }}', '  baz=qux/>'].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'last-attribute' }],\n    },\n\n    // element-open-end:new-line alone (no mustache-open-end set)\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff', '  }}', '  baz=qux', '/>'].join(\n        '\\n'\n      ),\n      options: [{ 'element-open-end': 'new-line' }],\n    },\n    // Inline mustache value in element\n    {\n      code: '<div\\n  foo={{action some stuff}}\\n  baz=qux\\n/>',\n      options: [{ 'element-open-end': 'new-line' }],\n    },\n\n    // Mustache with sub-expression\n    {\n      code: [\n        '{{my-component',\n        '  foo=bar',\n        '  baz=qux',\n        '  my-attr=(component \"my-other-component\" data=(hash',\n        '    foo=bar',\n        '    foo=bar',\n        '    baz=qux))',\n        '}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n    // Mustache with sub-expression and last-attribute close\n    {\n      code: [\n        '{{my-component',\n        '  foo=bar',\n        '  baz=qux',\n        '  my-attr=(component \"my-other-component\" data=(hash',\n        '    foo=bar',\n        '    foo=bar',\n        '    baz=qux))}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute' }],\n    },\n    // Element with nested mustache value\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff}}', '  baz=qux', '/>'].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'new-line' }],\n    },\n    // Element with nested mustache and new-line close\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff', '  }}', '  baz=qux', '/>'].join(\n        '\\n'\n      ),\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'new-line' }],\n    },\n    // Element with nested mustache, last-attribute for both\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff}}', '  baz=qux/>'].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'last-attribute' }],\n    },\n    // Angle bracket invocation\n    {\n      code: [\n        '<SiteHeader',\n        '  @selected={{this.user.country}} as |Option|',\n        '>{{#each this.availableCountries as |country|}}',\n        '<Option @value={{country}}>{{country.name}}</Option>',\n        '{{/each}}',\n        '</SiteHeader>',\n      ].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n    // Non-block HTML with wrong indentation but process-elements is off\n    {\n      code: '<input\\ndisabled\\n>',\n      options: [{ 'process-elements': false }],\n    },\n    // Block HTML element\n    {\n      code: '<a\\n  disabled\\n>abc\\n</a>',\n      options: [{ 'process-elements': true }],\n    },\n    // Block HTML element with last-attribute\n    {\n      code: '<a\\n  disabled>\\nabc\\n</a>',\n      options: [{ 'process-elements': true, 'element-open-end': 'last-attribute' }],\n    },\n    // Nested elements\n    {\n      code: ['<a', '  disabled', '>', '<span', '  class=\"abc\"', '>spam me', '</span>', '</a>'].join(\n        '\\n'\n      ),\n      options: [{ 'process-elements': true }],\n    },\n    // Element with nested each block\n    {\n      code: [\n        '<a',\n        '  disabled',\n        '>',\n        '{{#each',\n        '  class=\"abc\"',\n        '}}spam me',\n        '{{/each}}',\n        '</a>',\n      ].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n\n    // Element with inline mustache content after close\n    {\n      code: '<a\\n  disabled\\n>{{contact-details firstName lastName}}\\n</a>',\n      options: [{ 'process-elements': true }],\n    },\n    // Complex <a> with disabled={{if...}} and nested mustache children\n    {\n      code: [\n        '<a',\n        '  disabled={{if',\n        '    true',\n        '    (action \"mostPowerfulAction\" value=target.value)',\n        '    (action \"lessPowerfulAction\" value=target.value)',\n        '  }}',\n        '>{{contact-details',\n        '   firstName',\n        '   lastName',\n        ' }}',\n        '</a>',\n      ].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n    // Complex <a> with disabled={{if...}} and block mustache\n    {\n      code: [\n        '<a',\n        '  disabled={{if',\n        '    true',\n        '    (action \"mostPowerfulAction\" value=target.value)',\n        '    (action \"lessPowerfulAction\" value=target.value)',\n        '  }}',\n        '>{{#contact-details',\n        '   firstName',\n        '   lastName',\n        ' }}{{foo}}{{/contact-details}}',\n        '</a>',\n      ].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n    // Complex <a> with all last-attribute\n    {\n      code: [\n        '<a',\n        '  disabled={{if',\n        '    true',\n        '    (action \"mostPowerfulAction\" value=target.value)',\n        '    (action \"lessPowerfulAction\" value=target.value)}}>',\n        '{{#contact-details',\n        '  firstName',\n        '  lastName}}',\n        ' {{foo}}{{/contact-details}}',\n        '</a>',\n      ].join('\\n'),\n      options: [\n        {\n          'process-elements': true,\n          'element-open-end': 'last-attribute',\n          'mustache-open-end': 'last-attribute',\n        },\n      ],\n    },\n    // Complex <a> with element-open-end:new-line, mustache-open-end:last-attribute\n    {\n      code: [\n        '<a',\n        '  disabled={{if',\n        '    true',\n        '    (action \"mostPowerfulAction\" value=target.value)',\n        '    (action \"lessPowerfulAction\" value=target.value)}}',\n        '>',\n        '  {{#contact-details',\n        '    firstName',\n        '    lastName}}',\n        '  {{foo}}',\n        '  {{/contact-details}}',\n        '</a>',\n      ].join('\\n'),\n      options: [\n        {\n          'process-elements': true,\n          'element-open-end': 'new-line',\n          'mustache-open-end': 'last-attribute',\n        },\n      ],\n    },\n    // Complex <a> with element-open-end:last-attribute, mustache-open-end:new-line\n    {\n      code: [\n        '<a',\n        '  disabled={{if',\n        '    true',\n        '    (action \"mostPowerfulAction\" value=target.value)',\n        '    (action \"lessPowerfulAction\" value=target.value)',\n        '  }}>',\n        '  {{#contact-details',\n        '    firstName',\n        '    lastName',\n        '  }}',\n        '   {{foo}}{{/contact-details}}',\n        '</a>',\n      ].join('\\n'),\n      options: [\n        {\n          'process-elements': true,\n          'element-open-end': 'last-attribute',\n          'mustache-open-end': 'new-line',\n        },\n      ],\n    },\n\n    // Self closing single line with process-elements\n    {\n      code: '<div disabled />',\n      options: [{ 'process-elements': true }],\n    },\n    // Self closing multi line with process-elements\n    {\n      code: '<div\\n  disabled\\n/>',\n      options: [{ 'process-elements': true }],\n    },\n    // Non-block input multi line with process-elements\n    {\n      code: '<input\\n  disabled\\n>',\n      options: [{ 'process-elements': true }],\n    },\n    // Input with process-elements (single line)\n    {\n      code: '<input disabled>',\n      options: [{ 'process-elements': true }],\n    },\n    // Input with action attribute\n    {\n      code: '<input\\n  disabled={{action \"mostPowerfulAction\" value=target.value}}\\n>',\n      options: [{ 'process-elements': true }],\n    },\n    // Input with nested if in attribute\n    {\n      code: [\n        '<input',\n        '  disabled={{if',\n        '    true',\n        '    (action \"mostPowerfulAction\" value=target.value)',\n        '    (action \"lessPowerfulAction\" value=target.value)',\n        '  }}',\n        '>',\n      ].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n\n    // SomeThing with @long-arg={{hash}} last-attribute\n    {\n      code: ['<SomeThing', '  @long-arg={{hash', '    foo=\"bar\"}}', '/>'].join('\\n'),\n      options: [{ 'mustache-open-end': 'last-attribute' }],\n    },\n    // SomeThing with @long-arg={{hash}} new-line\n    {\n      code: ['<SomeThing', '  @long-arg={{hash', '    foo=\"bar\"', '  }}', '/>'].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n    // SomeThing with @long-arg and data-after-long-arg\n    [\n      '<SomeThing',\n      '  @long-arg={{hash',\n      '    foo=\"bar\"',\n      '  }}',\n      '  data-after-long-arg={{true}}',\n      '/>',\n    ].join('\\n'),\n\n    // Form with element modifier\n    [\n      '<form',\n      \"  class='form-signin'\",\n      \"  {{action 'authenticate' email password}}\",\n      '>',\n      '</form>',\n    ].join('\\n'),\n\n    // Triple stash\n    ['<div>', '  {{{i18n', '    param=true', '    otherParam=false', '  }}}', '</div>'].join('\\n'),\n\n    // Empty block form short\n    '{{#employee-details as |employee|}}{{employee.fullName}}{{/employee-details}}',\n    // Block with just positional params\n    {\n      code: [\n        '{{#employee-details',\n        '  firstName',\n        '  lastName',\n        '  age',\n        'as |employee|',\n        '}}',\n        '  {{employee.fullName}}',\n        '{{/employee-details}}',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n  ],\n\n  invalid: [\n    // Element with process-elements\n    {\n      code: '<input disabled\\n>',\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [{ messageId: 'incorrectParamIndentation' }, { messageId: 'incorrectCloseBracket' }],\n    },\n    // Self closing element with process-elements\n    {\n      code: '<div disabled\\n/>',\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [{ messageId: 'incorrectParamIndentation' }, { messageId: 'incorrectCloseBracket' }],\n    },\n    // Too long single-line input >80 chars with process-elements\n    {\n      code: '<input disabled type=\"text\" value=\"abc\" class=\"classy classic classist\" id=\"input-now\">',\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectCloseBracket' },\n      ],\n    },\n    // Element with element-open-end: last-attribute but close on new line\n    {\n      code: '<input\\n  foo=bar\\n  baz=bar\\n>',\n      output: null,\n      options: [{ 'element-open-end': 'last-attribute' }],\n      errors: [{ messageId: 'incorrectCloseBracket' }],\n    },\n    // Element with element-open-end: new-line but close on last attribute\n    {\n      code: '<input\\n  foo=bar\\n  baz=qux>',\n      output: null,\n      options: [{ 'element-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectCloseBracket' }],\n    },\n    // Mustache with mustache-open-end: last-attribute but close on new line\n    {\n      code: [\n        '{{my-component',\n        '  foo=bar',\n        '  baz=qux',\n        '  my-attr=(component \"my-other-component\" data=(hash',\n        '    foo=bar',\n        '    foo=bar',\n        '    baz=qux))',\n        '}}',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'last-attribute' }],\n      errors: [{ messageId: 'incorrectCloseBrace' }],\n    },\n    // Mustache with mustache-open-end: new-line but close on last attribute\n    {\n      code: [\n        '{{my-component',\n        '  foo=bar',\n        '  baz=qux',\n        '  my-attr=(component \"my-other-component\" data=(hash',\n        '    foo=bar',\n        '    foo=bar',\n        '    baz=qux))}}',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectCloseBrace' }],\n    },\n    // Element close brace: mixed mustache-open-end + element-open-end\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff}}', '  baz=qux/>'].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectCloseBrace' }, { messageId: 'incorrectCloseBracket' }],\n    },\n    // Element close brace: element-open-end: last-attribute but close on new line\n    {\n      code: ['<div', '  foo={{action', '    some', '    stuff', '  }}', '  baz=qux', '/>'].join(\n        '\\n'\n      ),\n      output: null,\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'last-attribute' }],\n      errors: [{ messageId: 'incorrectCloseBrace' }, { messageId: 'incorrectCloseBracket' }],\n    },\n    // Incorrect attribute indentation on mustache\n    {\n      code: ['{{contact-details', 'firstName=firstName', '  lastName=lastName', '}}'].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectParamIndentation' }],\n    },\n    // Block form attribute indentation wrong\n    {\n      code: ['{{#foo-bar', 'baz=true', '}}', '{{/foo-bar}}'].join('\\n'),\n      output: null,\n      errors: [{ messageId: 'incorrectParamIndentation' }],\n    },\n    // Incorrect attribute indentation on element\n    {\n      code: '<div\\nfoo=bar\\n  baz=qux\\n/>',\n      output: null,\n      options: [{ 'element-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectParamIndentation' }],\n    },\n    // Non-block form more than 30 characters\n    {\n      code: '{{contact-details firstName=firstName lastName=lastName}}',\n      output: null,\n      options: [{ 'open-invocation-max-len': 30 }],\n      errors: [\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectCloseBrace' },\n      ],\n    },\n    // Block form with wrong attr indent + block params + close brace\n    {\n      code: [\n        '{{#contact-details',\n        ' firstName=firstName lastName=lastName as |contact|}}',\n        ' {{contact.fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      output: null,\n      errors: [\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectBlockParamIndentation' },\n        { messageId: 'incorrectCloseBrace' },\n      ],\n    },\n    // Block form with close brace on wrong line\n    {\n      code: [\n        '{{#contact-details',\n        '  firstName=firstName',\n        '  lastName=lastName',\n        'as |fullName|}}',\n        '  {{fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      output: null,\n      errors: [{ messageId: 'incorrectCloseBrace' }],\n    },\n    // Block form > 80 chars with all on one line\n    {\n      code: [\n        '{{#contact-details firstName=firstName lastName=lastName age=age avatar=avatar as |contact|}}',\n        '  {{fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      output: null,\n      errors: [\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectBlockParamIndentation' },\n        { messageId: 'incorrectCloseBrace' },\n      ],\n    },\n    // Block form with empty lines before block params\n    {\n      code: [\n        '{{#contact-details',\n        '',\n        '',\n        'as |contact|}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n      ].join('\\n'),\n      output: null,\n      errors: [\n        { messageId: 'incorrectBlockParamIndentation' },\n        { messageId: 'incorrectCloseBrace' },\n      ],\n    },\n    // Helper > 80 chars\n    {\n      code: '{{if (or logout.isRunning (not session.isAuthenticated)) \"Logging Out...\" \"Log Out\"}}',\n      output: null,\n      options: [{ 'open-invocation-max-len': 80 }],\n      errors: [\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectParamIndentation' },\n        { messageId: 'incorrectCloseBrace' },\n      ],\n    },\n    // Mixed element + mustache: new-line/new-line with wrong close positions\n    {\n      code: [\n        '<div',\n        '  class=\"classy\">',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectCloseBracket' }, { messageId: 'incorrectCloseBrace' }],\n    },\n    // Mixed: last-attribute/last-attribute with wrong close positions\n    {\n      code: [\n        '<div',\n        '  class=\"classy\"',\n        '>',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |',\n        '}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'last-attribute' }],\n      errors: [{ messageId: 'incorrectCloseBracket' }, { messageId: 'incorrectCloseBrace' }],\n    },\n    // Mixed: last-attribute/new-line with wrong close positions\n    {\n      code: [\n        '<div',\n        '  class=\"classy\">',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |',\n        '}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'last-attribute', 'element-open-end': 'new-line' }],\n      errors: [{ messageId: 'incorrectCloseBracket' }, { messageId: 'incorrectCloseBrace' }],\n    },\n    // Mixed: new-line/last-attribute with wrong close positions\n    {\n      code: [\n        '<div',\n        '  class=\"classy\"',\n        '>',\n        '{{#contact-details',\n        '  param0',\n        '  param1=abc',\n        '  param2=abc',\n        'as |ab cd ef  cd ef |}}',\n        '  {{contact.fullName}}',\n        '{{/contact-details}}',\n        '</div>',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'new-line', 'element-open-end': 'last-attribute' }],\n      errors: [{ messageId: 'incorrectCloseBracket' }, { messageId: 'incorrectCloseBrace' }],\n    },\n    // Close brace on wrong line in nested block\n    {\n      code: ['{{#foo bar as |foo|}}', '    {{foo.bar', '      baz}}{{/foo}}'].join('\\n'),\n      output: null,\n      errors: [{ messageId: 'incorrectCloseBrace' }],\n    },\n  ],\n});\n\n// ---- GJS tests ----\n\ngjsRuleTester.run('template-attribute-indentation', rule, {\n  valid: [\n    // Short invocation\n    '<template>{{foo bar=baz}}</template>',\n    // Multi-line with proper indentation\n    {\n      code: [\n        '<template>',\n        '{{contact-details',\n        '  firstName=firstName',\n        '  lastName=lastName',\n        '}}',\n        '</template>',\n      ].join('\\n'),\n      options: [{ 'mustache-open-end': 'new-line' }],\n    },\n    // Element with proper indentation\n    {\n      code: ['<template>', '<div', '  foo=bar', '  baz=qux', '/>', '</template>'].join('\\n'),\n      options: [{ 'element-open-end': 'new-line' }],\n    },\n  ],\n  invalid: [\n    // Incorrect indentation in GJS\n    {\n      code: [\n        '<template>',\n        '{{contact-details',\n        'firstName=firstName',\n        '  lastName=lastName',\n        '}}',\n        '</template>',\n      ].join('\\n'),\n      output: null,\n      options: [{ 'mustache-open-end': 'new-line' }],\n      errors: [\n        {\n          messageId: 'incorrectParamIndentation',\n        },\n      ],\n    },\n  ],\n});\n\n// ---- Closing tag tests ----\n\nhbsRuleTester.run('template-attribute-indentation (closing tag)', rule, {\n  valid: [\n    // Closing tag correctly aligned with opening tag, after text content\n    {\n      code: ['<div', '  class=\"foo\"', '>', '  content', '</div>'].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n    // Closing tag correctly aligned, after element child\n    {\n      code: ['<div', '  class=\"foo\"', '>', '  <span>text</span>', '</div>'].join('\\n'),\n      options: [{ 'process-elements': true }],\n    },\n    // Short element — canApplyRule returns false (single-line, under maxLength)\n    {\n      code: '<div class=\"foo\">content</div>',\n      options: [{ 'process-elements': true }],\n    },\n  ],\n\n  invalid: [\n    // Closing tag indented too far (wrong column)\n    {\n      code: ['<div', '  class=\"foo\"', '>', '  content', '  </div>'].join('\\n'),\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [{ messageId: 'incorrectClosingTag' }],\n    },\n    // Closing tag on same line as content (wrong column)\n    {\n      code: ['<MyComponent', '  @arg=\"val\"', '>text</MyComponent>'].join('\\n'),\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [{ messageId: 'incorrectClosingTag' }],\n    },\n    // Closing tag indented when it should be at column 0\n    {\n      code: ['<MyComponent', '  @arg=\"val\"', '>', '  text', '    </MyComponent>'].join('\\n'),\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [{ messageId: 'incorrectClosingTag' }],\n    },\n    // Closing tag on same line as opening tag attributes (too early)\n    {\n      code: ['<div', '  class=\"foo\"', '>content</div>'].join('\\n'),\n      output: null,\n      options: [{ 'process-elements': true }],\n      errors: [{ messageId: 'incorrectClosingTag' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-attribute-order.js",
    "content": "const rule = require('../../../lib/rules/template-attribute-order');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-attribute-order', rule, {\n  valid: [\n    '<template><div class=\"foo\" id=\"bar\"></div></template>',\n    '<template><button class=\"btn\" role=\"button\" aria-label=\"Submit\"></button></template>',\n    '<template><input type=\"text\" name=\"username\" value=\"\"></template>',\n    '<template><div data-test-id=\"foo\"></div></template>',\n    // Single attribute - no ordering needed\n    '<template><div class=\"foo\"></div></template>',\n    // All same category\n    '<template><div aria-label=\"x\" aria-hidden=\"true\"></div></template>',\n    // Correct full order\n    '<template><input class=\"x\" id=\"y\" role=\"r\" aria-label=\"l\" data-test-foo=\"1\" type=\"text\" name=\"n\" value=\"v\" placeholder=\"p\" disabled></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><div id=\"bar\" class=\"foo\"></div></template>',\n      output: '<template><div class=\"foo\" id=\"bar\"></div></template>',\n      errors: [{ messageId: 'wrongOrder' }],\n    },\n    {\n      code: '<template><button aria-label=\"Submit\" role=\"button\"></button></template>',\n      output: '<template><button role=\"button\" aria-label=\"Submit\"></button></template>',\n      errors: [{ messageId: 'wrongOrder' }],\n    },\n    {\n      code: '<template><input name=\"username\" type=\"text\"></template>',\n      output: '<template><input type=\"text\" name=\"username\"></template>',\n      errors: [{ messageId: 'wrongOrder' }],\n    },\n    // Multiple attributes out of order (3 attrs, fully reversed)\n    {\n      code: '<template><div name=\"x\" id=\"y\" class=\"z\"></div></template>',\n      output: '<template><div id=\"y\" name=\"x\" class=\"z\"></div></template>',\n      errors: [{ messageId: 'wrongOrder' }, { messageId: 'wrongOrder' }],\n    },\n    // Unknown attributes go last\n    {\n      code: '<template><div custom=\"x\" class=\"y\"></div></template>',\n      output: '<template><div class=\"y\" custom=\"x\"></div></template>',\n      errors: [{ messageId: 'wrongOrder' }],\n    },\n    // data-test- prefix before type\n    {\n      code: '<template><input type=\"text\" data-test-input=\"true\"></template>',\n      output: '<template><input data-test-input=\"true\" type=\"text\"></template>',\n      errors: [{ messageId: 'wrongOrder' }],\n    },\n    // Multiline attributes\n    {\n      code: '<template><div\\n  name=\"x\"\\n  class=\"y\"\\n></div></template>',\n      output: '<template><div\\n  class=\"y\"\\n  name=\"x\"\\n></div></template>',\n      errors: [{ messageId: 'wrongOrder' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-block-indentation.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst editorConfigUtil = require('../../../lib/utils/editorconfig');\nconst rule = require('../../../lib/rules/template-block-indentation');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//\n// All tests are wrapped in a describe so beforeAll/afterAll can install a spy\n// on editorConfigUtil.resolveEditorConfig before any rule invocation. This\n// prevents the project's own .editorconfig (indent_size=2) from interfering\n// with editorconfig-specific test cases.\n//------------------------------------------------------------------------------\n\ndescribe('template-block-indentation', () => {\n  beforeAll(() => {\n    vi.spyOn(editorConfigUtil, 'resolveEditorConfig').mockReturnValue({});\n  });\n\n  afterAll(() => {\n    vi.restoreAllMocks();\n  });\n\n  const hbsRuleTester = new RuleTester({\n    parser: require.resolve('ember-eslint-parser/hbs'),\n    parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  });\n\n  const gjsRuleTester = new RuleTester({\n    parser: require.resolve('ember-eslint-parser'),\n    parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  });\n\n  // ---- HBS tests ----\n\n  hbsRuleTester.run('template-block-indentation', rule, {\n    valid: [\n      // Single line - no indentation issues\n      '{{#if foo}}bar{{/if}}',\n      '<div></div>',\n      '<div>foo</div>',\n      '<div><p>Stuff</p></div>',\n      '{{#link-to \"foo.bar\"}}Blah{{/link-to}}',\n      '{{#if foo}}<p>Hi!</p>{{/if}}',\n      '{{#if foo}}<p>Hi!</p>{{else}}<p>Bye!</p>{{/if}}',\n      '{{#if foo}}<p>Hi!</p>{{else if bar}}<p>Hello!</p>{{else}}<p>Bye!</p>{{/if}}',\n\n      // Properly indented block\n      ['{{#if foo}}', '  bar', '{{/if}}'].join('\\n'),\n\n      // Properly indented element\n      ['<div>', '  <p>{{t \"greeting\"}}</p>', '</div>'].join('\\n'),\n      ['<div>', '  <p>Stuff Here</p>', '</div>'].join('\\n'),\n      ['<div>', '  <p>Hi!</p>', '</div>'].join('\\n'),\n\n      // Nested blocks\n      ['{{#if foo}}', '  {{#if bar}}', '    baz', '  {{/if}}', '{{/if}}'].join('\\n'),\n\n      // Properly indented with else\n      ['{{#if foo}}', '  bar', '{{else}}', '  baz', '{{/if}}'].join('\\n'),\n\n      // Properly indented with else if\n      ['{{#if foo}}', '  bar', '{{else if baz}}', '  qux', '{{/if}}'].join('\\n'),\n\n      // Complex if/else if/else\n      [\n        '{{#if isMorning}}',\n        '  Good morning',\n        '{{else foo-bar isAfternoon}}',\n        '  Good afternoon',\n        '{{else}}',\n        '  Good night',\n        '{{/if}}',\n      ].join('\\n'),\n\n      // Nested if/else inside element\n      [\n        '<div>',\n        '  {{#if isMorning}}',\n        '    Good morning',\n        '  {{else if isAfternoon}}',\n        '    Good afternoon',\n        '  {{else}}',\n        '    Good night',\n        '  {{/if}}',\n        '</div>',\n      ].join('\\n'),\n\n      // Void elements (no children to check)\n      '<br>',\n      '<input>',\n      '<img>',\n      '<hr>',\n\n      // Multi-line input with attributes\n      ['<input ', '  data-foo=\"blah\"', '  data-bar=\"derp\"', '  data-qux=\"blammo\">'].join('\\n'),\n\n      // Content on same line as open/close\n      ['<div>', '  foo bar baz', '</div>'].join('\\n'),\n\n      // Leading content on the same line\n      ['{{#if foo}}', '  <span>bar</span> baz', '{{/if}}'].join('\\n'),\n      [\n        '<div>',\n        '  <span>Foo</span>{{#some-thing}}<p>lorum ipsum</p>{{/some-thing}}',\n        '</div>',\n      ].join('\\n'),\n\n      // Escaped curlies\n      ['<h1>Header</h1>', '<div>', '  \\\\{{example}}', '</div>'].join('\\n'),\n      ['<div>', '  \\\\{{example}}', '</div>'].join('\\n'),\n\n      // Comment with content\n      ['<div>', '  {{! What a comment}}', '  {{foo-bar}}', '</div>'].join('\\n'),\n\n      // Raw blocks\n      ['{{{{if isMorning}}}}', '  Good Morning', '{{{{/if}}}}'].join('\\n'),\n\n      // Each with newline\n      '\\n{{#each cats as |dog|}}\\n{{/each}}',\n\n      // Mustache expressions inside blocks\n      ['{{#if foo}}', '  {{foo}}-{{bar}}', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  Foo-{{bar}}', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  Foo:', '  {{bar}}', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  {{foo}}:', '  {{bar}}', '{{/if}}'].join('\\n'),\n\n      // Tilde (whitespace control) variations\n      ['{{#if foo}}', '  <div></div>', '{{~/if}}'].join('\\n'),\n      ['{{~#if foo}}', '  <div></div>', '{{/if}}'].join('\\n'),\n      ['{{#if foo~}}', '  <div></div>', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  <div></div>', '{{/if~}}'].join('\\n'),\n      ['{{#if foo}}', '  <div></div>', '{{~else}}', '  <div></div>', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  <div></div>', '{{else~}}', '  <div></div>', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  <div></div>', '{{~else~}}', '  <div></div>', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  <div></div>', '{{else}}', '  <div></div>', '{{~/if~}}'].join('\\n'),\n      ['{{#if foo~}}', '  -', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '{{else if bar}}', '{{else}}', '  {{#if baz}}', '  {{/if~}}', '{{/if}}'].join(\n        '\\n'\n      ),\n      [\n        '{{#if foo}}',\n        '  <div>do foo</div>',\n        '{{else if bar~}}',\n        '  <div>do bar</div>',\n        '{{/if}}',\n      ].join('\\n'),\n\n      // Multi-line attribute element\n      ['<div class=\"multi\"', '     id=\"lines\"></div>'].join('\\n'),\n\n      // HTML entities\n      ['{{#if foo}}', '  &nbsp;Hello', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  &nbsp;<div></div>', '{{/if}}'].join('\\n'),\n      ['{{#if foo}}', '  &nbsp;bar', '{{/if}}'].join('\\n'),\n\n      // 4-space indentation config\n      {\n        code: ['<div>', '    <p>Hello</p>', '</div>'].join('\\n'),\n        options: [4],\n      },\n      {\n        code: ['<div>', '    <p>LOLOL!</p>', '</div>'].join('\\n'),\n        options: [4],\n      },\n\n      // Tab config (1-space indent)\n      {\n        code: ['<div>', ' <p>Hello</p>', '</div>'].join('\\n'),\n        options: ['tab'],\n      },\n      {\n        code: ['<div>', '\\t<p>Hi!</p>', '</div>'].join('\\n'),\n        options: ['tab'],\n      },\n\n      // Object config\n      {\n        code: ['<div>', '    <p>Hello</p>', '</div>'].join('\\n'),\n        options: [{ indentation: 4 }],\n      },\n\n      // Ignored elements - pre, script, style, textarea\n      ['<pre>', 'no indentation needed', '  or checked', '</pre>'].join('\\n'),\n      '<pre>\\nsome text</pre>',\n      '<script>\\nsome text</script>',\n      '<textarea>\\nsome text</textarea>',\n      '<style>\\nsome text</style>',\n      '<textarea> \\n<div>\\nsome text   \\n \\n </div></textarea>',\n      '<pre>\\n{{#foo}}\\n {{#baz}}  hi!\\n   {{derp}}{{/baz}}{{/foo}}\\n </pre>',\n      '<script>\\n{{#foo}}\\n {{#baz}}  hi!\\n   {{derp}}{{/baz}}{{/foo}}\\n </script>',\n      '<style>\\n{{#foo}}\\n {{#baz}}  hi!\\n   {{derp}}{{/baz}}{{/foo}}\\n </style>',\n      '<textarea>\\n{{#foo}}\\n {{#baz}}  hi!\\n   {{derp}}{{/baz}}{{/foo}}\\n </textarea>',\n\n      // ignoreComments config\n      {\n        code: ['<div>', '<!-- Comment -->', '{{! Comment }}', '</div>'].join('\\n'),\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: [\n          '{{#if foo}}',\n          '<!-- Comment -->',\n          '  {{foo}}',\n          '{{else}}',\n          '  {{bar}}',\n          '{{! Comment }}',\n          '{{/if}}',\n        ].join('\\n'),\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: '  {{! Comment }}<div>foo</div>',\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: '<div>{{! Comment }}</div>',\n        options: [{ ignoreComments: true }],\n      },\n      // Note: ignoreComments ignores comments but text after comment is still checked\n      // '<div>\\n{{! Comment }}foo\\n</div>' is invalid even with ignoreComments: true\n      {\n        code: '<div>foo</div>{{! Comment }}',\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: '  <!-- Comment --><div>foo</div>',\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: '<div><!-- Comment --></div>',\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: '<div>foo</div><!-- Comment -->',\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: [\n          '{{#if foo}}',\n          '<!-- Comment -->',\n          '  <!-- Comment -->',\n          '  {{#each bar as |baz|}}',\n          '{{! Comment }}',\n          '    {{#each baz as |a|}}',\n          '      {{! Comment }}',\n          '      {{a}}',\n          '<!-- Comment -->',\n          '    {{/each}}',\n          '{{! Comment }}',\n          '  {{/each}}',\n          '<!-- Comment -->',\n          '{{! Comment }}',\n          '{{else}}',\n          ' {{! Comment }}',\n          '{{/if}}',\n        ].join('\\n'),\n        options: [{ ignoreComments: true }],\n      },\n      {\n        code: ['<div>', '  <!-- Comment -->', '  {{! Comment }}', '</div>'].join('\\n'),\n        options: [{ ignoreComments: false }],\n      },\n      {\n        code: [\n          '{{#if foo}}',\n          '  <!-- Comment -->',\n          '  {{foo}}',\n          '{{else}}',\n          '  {{bar}}',\n          '  {{! Comment }}',\n          '{{/if}}',\n        ].join('\\n'),\n        options: [{ ignoreComments: false }],\n      },\n\n      // Inline content with span\n      'relativeDate <span class=\"my-apps-date-connected__absolute\">(absoluteDate)</span>',\n\n      // Title and path elements\n      '<title></title>\\n<path/><path/>',\n\n      // Empty block\n      ['<div>', '</div>'].join('\\n'),\n\n      // Component invocation\n      ['<MyComponent>', '  <span>content</span>', '</MyComponent>'].join('\\n'),\n\n      // Nested component invocation with block params\n      [\n        '{{#foo-bar as |baz|}}',\n        '  {{#baz.content}}',\n        '    {{#component \"foo-bar\"}}',\n        '      Content',\n        '    {{/component}}',\n        '  {{/baz.content}}',\n        '{{/foo-bar}}',\n      ].join('\\n'),\n\n      // Block with inline else\n      ['{{#each items as |item|}}', '  {{item.name}}', '{{/each}}'].join('\\n'),\n\n      // Comment with proper indentation\n      ['<div>', '  {{foo-bar baz=\"asdf\"}}', '  <!-- foo bar baz -->', '</div>'].join('\\n'),\n\n      // String literal\n      \"{{'this works'}}\",\n    ],\n\n    invalid: [\n      // Incorrect end indentation\n      {\n        code: ['{{#if foo}}', '  bar', '  {{/if}}'].join('\\n'),\n\n        output: '{{#if foo}}\\n  bar\\n{{/if}}',\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Incorrect child indentation - missing indent\n      {\n        code: ['<div>', '<p>{{t \"greeting\"}}</p>', '</div>'].join('\\n'),\n\n        output: '<div>\\n  <p>{{t \"greeting\"}}</p>\\n</div>',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Incorrect child indentation - too much\n      {\n        code: ['<div>', '    <p>{{t \"greeting\"}}</p>', '</div>'].join('\\n'),\n\n        output: '<div>\\n  <p>{{t \"greeting\"}}</p>\\n</div>',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Incorrect end indentation for element\n      {\n        code: ['<div>', '  <p>content</p>', '  </div>'].join('\\n'),\n\n        output: '<div>\\n  <p>content</p>\\n</div>',\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Incorrect else indentation\n      {\n        code: ['{{#if foo}}', '  bar', '  {{else}}', '  baz', '{{/if}}'].join('\\n'),\n\n        output: '{{#if foo}}\\n  bar\\n{{else}}\\n  baz\\n{{/if}}',\n        errors: [{ messageId: 'incorrectElse' }],\n      },\n\n      // Incorrect indentation with 4-space config\n      {\n        code: ['<div>', '  <p>Hello</p>', '</div>'].join('\\n'),\n\n        output: '<div>\\n    <p>Hello</p>\\n</div>',\n        options: [4],\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n      {\n        code: ['<div>', '  <p>Hi!</p>', '</div>'].join('\\n'),\n\n        output: '<div>\\n    <p>Hi!</p>\\n</div>',\n        options: [4],\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Multiple errors - wrong children and end\n      {\n        code: ['<div>', 'foo', '  </div>'].join('\\n'),\n\n        output: '<div>\\n  foo\\n</div>',\n        errors: [{ messageId: 'incorrectChild' }, { messageId: 'incorrectEnd' }],\n      },\n\n      // Nested indentation error\n      {\n        code: ['{{#if foo}}', '  {{#if bar}}', '  baz', '  {{/if}}', '{{/if}}'].join('\\n'),\n\n        output: '{{#if foo}}\\n  {{#if bar}}\\n    baz\\n  {{/if}}\\n{{/if}}',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Note: '<div>\\n  </div>' is not caught by this rule (empty element)\n\n      // Closing tag on same line as content but wrong indent\n      {\n        code: '<div>\\n  <p>Stuff goes here</p></div>',\n\n        output: null,\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Child not indented in element\n      {\n        code: '<div>\\n<p>Stuff goes here</p>\\n</div>',\n\n        output: '<div>\\n  <p>Stuff goes here</p>\\n</div>',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Child not indented in block\n      {\n        code: '{{#if}}\\n<p>Stuff goes here</p>\\n{{/if}}',\n\n        output: '{{#if}}\\n  <p>Stuff goes here</p>\\n{{/if}}',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Else in nested if with wrong end indent\n      {\n        code: [\n          '{{#if isMorning}}',\n          '{{else}}',\n          '  {{#if something}}',\n          '    Good night',\n          '    {{/if}}',\n          '{{/if}}',\n        ].join('\\n'),\n\n        output:\n          '{{#if isMorning}}\\n{{else}}\\n  {{#if something}}\\n    Good night\\n  {{/if}}\\n{{/if}}',\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Mixed indent - some children correct, some not\n      {\n        code: '<div>\\n  {{foo}}\\n{{bar}}\\n</div>',\n\n        output: '<div>\\n  {{foo}}\\n  {{bar}}\\n</div>',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Text child not indented\n      {\n        code: '<div>\\n  Foo:\\n{{bar}}\\n</div>',\n\n        output: '<div>\\n  Foo:\\n  {{bar}}\\n</div>',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Closing block ends at wrong column when preceded by content on same line\n      {\n        code: '<div>\\n  <span>Foo</span>{{#some-thing}}\\n  {{/some-thing}}\\n</div>',\n\n        output:\n          '<div>\\n  <span>Foo</span>{{#some-thing}}\\n                  {{/some-thing}}\\n</div>',\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Element closing tag at wrong indent when preceded by content\n      {\n        code: '{{#if foo}}\\n  {{foo}} <p>\\n            Bar\\n  </p>\\n{{/if}}',\n\n        output: '{{#if foo}}\\n  {{foo}} <p>\\n            Bar\\n          </p>\\n{{/if}}',\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Else block indentation error\n      {\n        code: ['{{#if foo}}', '  {{else}}', '{{/if}}'].join('\\n'),\n\n        output: '{{#if foo}}\\n{{else}}\\n{{/if}}',\n        errors: [{ messageId: 'incorrectElse' }],\n      },\n\n      // Tilde with wrong else/end\n      {\n        code: [\n          '{{#if foo}}',\n          '{{else if bar}}',\n          '{{else}}',\n          '  {{#if baz}}',\n          '  {{/if~}}',\n          '  {{/if}}',\n        ].join('\\n'),\n\n        output: '{{#if foo}}\\n{{else if bar}}\\n{{else}}\\n  {{#if baz}}\\n  {{/if~}}\\n{{/if}}',\n        errors: [{ messageId: 'incorrectEnd' }],\n      },\n\n      // Each with wrong else indent\n      {\n        code: ['{{#each foo as |bar|}}', '  {{else}}', '{{/each}}'].join('\\n'),\n\n        output: '{{#each foo as |bar|}}\\n{{else}}\\n{{/each}}',\n        errors: [{ messageId: 'incorrectElse' }],\n      },\n\n      // Note: comment with incorrect indentation is not flagged by this rule\n\n      // Tilde with wrong child indent\n      {\n        code: [\n          '{{#if isMorning}}',\n          '  Good morning',\n          '{{else if isAfternoon~}}',\n          '    Good afternoon',\n          '{{/if}}',\n        ].join('\\n'),\n\n        output:\n          '{{#if isMorning}}\\n  Good morning\\n{{else if isAfternoon~}}\\n  Good afternoon\\n{{/if}}',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Inline else with wrong position\n      {\n        code: ['{{#if foo}}foo{{else}}', '  bar', '{{/if}}'].join('\\n'),\n\n        output: null,\n        errors: [{ messageId: 'incorrectChild' }, { messageId: 'incorrectElse' }],\n      },\n\n      // Block params with wrong child indent\n      {\n        code: [\n          '{{#foo bar as |foobar|}}',\n          '   {{#foobar.baz}}{{/foobar.baz}}',\n          '   {{foobar.baz}}',\n          '{{/foo}}',\n        ].join('\\n'),\n\n        output:\n          '{{#foo bar as |foobar|}}\\n  {{#foobar.baz}}{{/foobar.baz}}\\n  {{foobar.baz}}\\n{{/foo}}',\n        errors: [{ messageId: 'incorrectChild' }, { messageId: 'incorrectChild' }],\n      },\n\n      // ignoreComments: true still catches non-comment child errors\n      {\n        code: ['<div>', 'test{{! Comment }}', '</div>'].join('\\n'),\n\n        output: '<div>\\n  test{{! Comment }}\\n</div>',\n        options: [{ ignoreComments: true }],\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n    ],\n  });\n\n  // ---- GJS tests ----\n\n  gjsRuleTester.run('template-block-indentation', rule, {\n    valid: [\n      // Single line inside template\n      '<template>{{#if foo}}bar{{/if}}</template>',\n\n      // Properly indented\n      ['<template>', '{{#if foo}}', '  bar', '{{/if}}', '</template>'].join('\\n'),\n\n      // Element properly indented\n      [\n        '<template>',\n        '  <div class=\"parent\">',\n        '    <div class=\"child\"></div>',\n        '  </div>',\n        '</template>',\n      ].join('\\n'),\n\n      // If/else in GJS template\n      [\n        '<template>',\n        '  {{#if foo}}',\n        '    {{foo}}',\n        '  {{else}}',\n        '    {{bar}}',\n        '  {{/if}}',\n        '</template>',\n      ].join('\\n'),\n\n      // If/else if/else in GJS\n      [\n        '<template>',\n        '  {{#if foo}}',\n        '    {{foo}}',\n        '  {{else if bar}}',\n        '    {{bar}}',\n        '  {{else}}',\n        '    {{baz}}',\n        '  {{/if}}',\n        '</template>',\n      ].join('\\n'),\n    ],\n    invalid: [\n      // Incorrect child indentation in GJS\n      {\n        code: ['<template>', '<div>', '<p>Hello</p>', '</div>', '</template>'].join('\\n'),\n\n        output: '<template>\\n<div>\\n  <p>Hello</p>\\n</div>\\n</template>',\n        errors: [{ messageId: 'incorrectChild' }],\n      },\n\n      // Else block with wrong indent in GJS\n      {\n        code: [\n          '<template>',\n          '  {{#if foo}}',\n          '    {{foo}}',\n          '    {{else if bar}}',\n          '    {{bar}}',\n          '  {{/if}}',\n          '</template>',\n        ].join('\\n'),\n\n        output:\n          '<template>\\n  {{#if foo}}\\n    {{foo}}\\n  {{else if bar}}\\n      {{bar}}\\n  {{/if}}\\n</template>',\n        errors: [{ messageId: 'incorrectElse' }, { messageId: 'incorrectChild' }],\n      },\n\n      // Nested else block with wrong indent in GJS\n      {\n        code: [\n          '<template>',\n          '  {{#if a}}',\n          '    {{#if foo}}',\n          '      {{foo}}',\n          '      {{else if bar}}',\n          '      {{bar}}',\n          '    {{/if}}',\n          '  {{/if}}',\n          '</template>',\n        ].join('\\n'),\n\n        output:\n          '<template>\\n  {{#if a}}\\n    {{#if foo}}\\n      {{foo}}\\n    {{else if bar}}\\n        {{bar}}\\n    {{/if}}\\n  {{/if}}\\n</template>',\n        errors: [{ messageId: 'incorrectElse' }, { messageId: 'incorrectChild' }],\n      },\n    ],\n  });\n\n  //----------------------------------------------------------------------------\n  // EditorConfig integration tests\n  //----------------------------------------------------------------------------\n\n  describe('editorconfig integration', () => {\n    const hbsRuleTesterEditorConfig = new RuleTester({\n      parser: require.resolve('ember-eslint-parser/hbs'),\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    });\n\n    afterEach(() => {\n      editorConfigUtil.resolveEditorConfig.mockReturnValue({});\n    });\n\n    describe('indent_size: 4 from editorconfig used as default', () => {\n      beforeEach(() => {\n        editorConfigUtil.resolveEditorConfig.mockReturnValue({ indent_size: 4 });\n      });\n\n      hbsRuleTesterEditorConfig.run('template-block-indentation', rule, {\n        valid: [\n          // 4-space indent is correct when editorconfig says indent_size=4\n          ['<div>', '    <p>Hi!</p>', '</div>'].join('\\n'),\n        ],\n        invalid: [\n          // 2-space indent is wrong when editorconfig says indent_size=4\n          {\n            code: ['<div>', '  <p>Hi!</p>', '</div>'].join('\\n'),\n\n            output: '<div>\\n    <p>Hi!</p>\\n</div>',\n            errors: [{ messageId: 'incorrectChild' }],\n          },\n        ],\n      });\n    });\n\n    describe('explicit option overrides editorconfig', () => {\n      beforeEach(() => {\n        editorConfigUtil.resolveEditorConfig.mockReturnValue({ indent_size: 4 });\n      });\n\n      hbsRuleTesterEditorConfig.run('template-block-indentation', rule, {\n        valid: [\n          // explicit option=2 wins over editorconfig indent_size=4\n          {\n            code: ['<div>', '  <p>Hi!</p>', '</div>'].join('\\n'),\n            options: [2],\n          },\n        ],\n        invalid: [\n          // 4-space indent is wrong when explicit option says 2\n          {\n            code: ['<div>', '    <p>Hi!</p>', '</div>'].join('\\n'),\n\n            output: '<div>\\n  <p>Hi!</p>\\n</div>',\n            options: [2],\n            errors: [{ messageId: 'incorrectChild' }],\n          },\n        ],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/template-builtin-component-arguments.js",
    "content": "const rule = require('../../../lib/rules/template-builtin-component-arguments');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-builtin-component-arguments', rule, {\n  valid: [\n    '<template><input type=\"text\" size=\"10\" /></template>',\n    '<template><Input @type=\"text\" size=\"10\" /></template>',\n    '<template><Input @type=\"checkbox\" @checked={{true}} /></template>',\n    '<template><Textarea @value=\"Tomster\" /></template>',\n    // In GJS/GTS: custom Input/Textarea components (not imported from @ember/component) are fine\n    // https://github.com/ember-template-lint/ember-template-lint/issues/2786\n    {\n      filename: 'test.gjs',\n      code: 'import { Input } from \"my-custom-lib\"; <template><Input type=\"text\" /></template>',\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><Input type=\"text\" /></template>',\n    },\n    {\n      filename: 'test.gts',\n      code: '<template><Textarea value=\"text\" /></template>',\n    },\n  ],\n  invalid: [\n    // In GJS/GTS: only flag when imported from @ember/component\n    {\n      filename: 'test.gjs',\n      code: 'import { Input } from \"@ember/component\"; <template><Input type=\"text\" size=\"10\" /></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Setting the `type` attribute on the builtin <Input> component is not allowed. Did you mean `@type`?',\n        },\n      ],\n    },\n    {\n      filename: 'test.gjs',\n      code: 'import { Input as MyInput } from \"@ember/component\"; <template><MyInput type=\"text\" /></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Setting the `type` attribute on the builtin <Input> component is not allowed. Did you mean `@type`?',\n        },\n      ],\n    },\n    {\n      filename: 'test.gjs',\n      code: 'import { Textarea } from \"@ember/component\"; <template><Textarea value=\"Tomster\" /></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Setting the `value` attribute on the builtin <Textarea> component is not allowed. Did you mean `@value`?',\n        },\n      ],\n    },\n  ],\n});\n\n// HBS tests — loose mode, always checks by tag name\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-builtin-component-arguments (hbs)', rule, {\n  valid: [\n    '<input type=\"text\" size=\"10\" />',\n    '<Input @type=\"text\" size=\"10\" />',\n    '<Input @type=\"checkbox\" @checked={{true}} />',\n    '<Textarea @value=\"Tomster\" />',\n  ],\n  invalid: [\n    {\n      code: '<Input type=\"text\" size=\"10\" />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Setting the `type` attribute on the builtin <Input> component is not allowed. Did you mean `@type`?',\n        },\n      ],\n    },\n    {\n      code: '<Input @type=\"checkbox\" checked />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Setting the `checked` attribute on the builtin <Input> component is not allowed. Did you mean `@checked`?',\n        },\n      ],\n    },\n    {\n      code: '<Textarea value=\"Tomster\" />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Setting the `value` attribute on the builtin <Textarea> component is not allowed. Did you mean `@value`?',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-deprecated-inline-view-helper.js",
    "content": "const rule = require('../../../lib/rules/template-deprecated-inline-view-helper');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-deprecated-inline-view-helper', rule, {\n  valid: [\n    '<template><MyComponent /></template>',\n    '<template>{{view}}</template>',\n    '<template>{{great-fishsticks}}</template>',\n    '<template>{{input placeholder=(t \"email\") value=email}}</template>',\n    '<template>{{title \"CrossCheck Web\" prepend=true separator=\" | \"}}</template>',\n    '<template>{{false}}</template>',\n    '<template>{{\"foo\"}}</template>',\n    '<template>{{42}}</template>',\n    '<template>{{null}}</template>',\n    '<template>{{undefined}}</template>',\n    '<template>{{has-block \"view\"}}</template>',\n    '<template>{{yield to=\"view\"}}</template>',\n    '<template>{{#if (has-block \"view\")}}{{yield to=\"view\"}}{{/if}}</template>',\n    '<template>{{this.view}}</template>',\n    '<template>{{@view}}</template>',\n    '<template>{{#let this.prop as |view|}} {{view}} {{/let}}</template>',\n    // isLocal: view is a block param, view.name should not be flagged\n    '<template>{{#each items as |view|}} {{view.name}} {{/each}}</template>',\n    // yield with view hash pair should not be flagged\n    '<template>{{yield hash=view.foo}}</template>',\n    // hash pair with key \"to\" should not be flagged\n    '<template>{{some-component to=view.foo}}</template>',\n    // Rule is HBS-only: `view` in GJS/GTS may be a legitimate imported JS binding\n    {\n      filename: 'test.gjs',\n      code: \"<template>{{view 'awful-fishsticks'}}</template>\",\n    },\n    {\n      filename: 'test.gts',\n      code: '<template>{{view.bad-fishsticks}}</template>',\n    },\n  ],\n  invalid: [\n    {\n      code: '<template>{{view class=\"foo\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: \"<template>{{view 'awful-fishsticks'}}</template>\",\n      output: '<template>{{awful-fishsticks}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: '<template>{{view.bad-fishsticks}}</template>',\n      output: '<template>{{bad-fishsticks}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: '<template>{{view.terrible.fishsticks}}</template>',\n      output: '<template>{{terrible.fishsticks}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: '<template>{{foo-bar bab=good baz=view.qux.qaz boo=okay}}</template>',\n      output: '<template>{{foo-bar bab=good baz=qux.qaz boo=okay}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: '<template><div class={{view.something}}></div></template>',\n      output: '<template><div class={{something}}></div></template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: '<template><div class=\"whatever-class\" data-foo={{view.hallo}} sure=thing></div></template>',\n      output:\n        '<template><div class=\"whatever-class\" data-foo={{hallo}} sure=thing></div></template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: '<template>{{#foo-bar derp=view.whoops thing=whatever}}{{/foo-bar}}</template>',\n      output: '<template>{{#foo-bar derp=whoops thing=whatever}}{{/foo-bar}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-deprecated-inline-view-helper', rule, {\n  valid: [\n    '{{great-fishsticks}}',\n    '{{input placeholder=(t \"email\") value=email}}',\n    '{{title \"CrossCheck Web\" prepend=true separator=\" | \"}}',\n    '{{false}}',\n    '{{\"foo\"}}',\n    '{{42}}',\n    '{{null}}',\n    '{{undefined}}',\n    '{{has-block \"view\"}}',\n    '{{yield to=\"view\"}}',\n    '{{#if (has-block \"view\")}}{{yield to=\"view\"}}{{/if}}',\n    '{{this.view}}',\n    '{{@view}}',\n    '{{#let this.prop as |view|}} {{view}} {{/let}}',\n    // isLocal: view is a block param, view.name should not be flagged\n    '{{#each items as |view|}} {{view.name}} {{/each}}',\n    // yield with view hash pair should not be flagged\n    '{{yield hash=view.foo}}',\n    // hash pair with key \"to\" should not be flagged\n    '{{some-component to=view.foo}}',\n  ],\n  invalid: [\n    {\n      code: \"{{view 'awful-fishsticks'}}\",\n      output: '{{awful-fishsticks}}',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n    {\n      code: '{{view.bad-fishsticks}}',\n      output: '{{bad-fishsticks}}',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n    {\n      code: '{{view.terrible.fishsticks}}',\n      output: '{{terrible.fishsticks}}',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n    {\n      code: '{{foo-bar bab=good baz=view.qux.qaz boo=okay}}',\n      output: '{{foo-bar bab=good baz=qux.qaz boo=okay}}',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n    {\n      code: '<div class={{view.something}}></div>',\n      output: '<div class={{something}}></div>',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n    {\n      code: '<div class=\"whatever-class\" data-foo={{view.hallo}} sure=thing></div>',\n      output: '<div class=\"whatever-class\" data-foo={{hallo}} sure=thing></div>',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n    {\n      code: '{{#foo-bar derp=view.whoops thing=whatever}}{{/foo-bar}}',\n      output: '{{#foo-bar derp=whoops thing=whatever}}{{/foo-bar}}',\n      errors: [\n        {\n          message:\n            'The inline form of `view` is deprecated. Please use `Ember.Component` instead. See http://emberjs.com/deprecations/v1.x/#toc_ember-view',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-deprecated-render-helper.js",
    "content": "const rule = require('../../../lib/rules/template-deprecated-render-helper');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-deprecated-render-helper', rule, {\n  valid: [\n    '<template><MyComponent /></template>',\n    '<template>{{this.render}}</template>',\n    '<template>{{valid-compoennt}}</template>',\n    '<template>{{input placeholder=(t \"email\") value=email}}</template>',\n    '<template>{{title \"CrossCheck Web\" prepent=true separator=\" | \"}}</template>',\n    '<template>{{hockey-player teamName=\"Boston Bruins\"}}</template>',\n    '<template>{{false}}</template>',\n    '<template>{{\"foo\"}}</template>',\n    '<template>{{42}}</template>',\n    '<template>{{null}}</template>',\n    '<template>{{undefined}}</template>',\n    // Rule is HBS-only: `render` in GJS/GTS is a JS binding, not the classic helper\n    {\n      filename: 'test.gjs',\n      code: '<template>{{render \"user\"}}</template>',\n    },\n    {\n      filename: 'test.gts',\n      code: '<template>{{render \"user\"}}</template>',\n    },\n  ],\n  invalid: [\n    {\n      code: '<template>{{render \"user\"}}</template>',\n      output: '<template>{{user}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: \"<template>{{render 'ken-griffey'}}</template>\",\n      output: '<template>{{ken-griffey}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: \"<template>{{render 'baseball-player' pitcher}}</template>\",\n      output: '<template>{{baseball-player model=pitcher}}</template>',\n      errors: [{ messageId: 'deprecated' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-deprecated-render-helper', rule, {\n  valid: [\n    '{{valid-compoennt}}',\n    '{{input placeholder=(t \"email\") value=email}}',\n    '{{title \"CrossCheck Web\" prepent=true separator=\" | \"}}',\n    '{{hockey-player teamName=\"Boston Bruins\"}}',\n    '{{false}}',\n    '{{\"foo\"}}',\n    '{{42}}',\n    '{{null}}',\n    '{{undefined}}',\n  ],\n  invalid: [\n    {\n      code: \"{{render 'ken-griffey'}}\",\n      output: '{{ken-griffey}}',\n      errors: [{ messageId: 'deprecated' }],\n    },\n    {\n      code: \"{{render 'baseball-player' pitcher}}\",\n      output: '{{baseball-player model=pitcher}}',\n      errors: [{ messageId: 'deprecated' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-eol-last.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst editorConfigUtil = require('../../../lib/utils/editorconfig');\nconst rule = require('../../../lib/rules/template-eol-last');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//\n// All tests are wrapped in a describe so beforeAll/afterAll can install a spy\n// on editorConfigUtil.resolveEditorConfig before any rule invocation. This\n// prevents the project's own .editorconfig from influencing test outcomes when\n// the 'editorconfig' option is used.\n//------------------------------------------------------------------------------\n\ndescribe('template-eol-last', () => {\n  beforeAll(() => {\n    vi.spyOn(editorConfigUtil, 'resolveEditorConfig').mockReturnValue({});\n  });\n\n  afterAll(() => {\n    vi.restoreAllMocks();\n  });\n\n  const ruleTester = new RuleTester({\n    parser: require.resolve('ember-eslint-parser'),\n    parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  });\n\n  ruleTester.run('template-eol-last', rule, {\n    valid: [\n      // In gjs/gts mode, eol-last is a no-op (file-level eol-last is handled by eslint core)\n      `<template>\n<div>test</div>\n</template>`,\n      '<template><div>test</div></template>',\n    ],\n\n    invalid: [],\n  });\n\n  const hbsRuleTester = new RuleTester({\n    parser: require.resolve('ember-eslint-parser/hbs'),\n    parserOptions: {\n      ecmaVersion: 2022,\n      sourceType: 'module',\n    },\n  });\n\n  hbsRuleTester.run('template-eol-last', rule, {\n    valid: [\n      // default 'always' — ends with newline\n      'test\\n',\n      '<img>\\n',\n      '<div>test</div>\\n',\n      '{{#my-component}}\\n  test\\n{{/my-component}}\\n',\n      // config 'never' — does not end with newline\n      { code: 'test', options: ['never'] },\n      { code: '<img>', options: ['never'] },\n      { code: '<div>test</div>', options: ['never'] },\n      { code: '{{#my-component}}\\n  test\\n{{/my-component}}', options: ['never'] },\n    ],\n\n    invalid: [\n      // default 'always' — missing newline\n      {\n        code: 'test',\n        output: 'test\\n',\n        options: ['always'],\n        errors: [{ messageId: 'mustEnd' }],\n      },\n      {\n        code: '<img>',\n        output: '<img>\\n',\n        options: ['always'],\n        errors: [{ messageId: 'mustEnd' }],\n      },\n      {\n        code: '<div>test</div>',\n        output: '<div>test</div>\\n',\n        options: ['always'],\n        errors: [{ messageId: 'mustEnd' }],\n      },\n      // config 'never' — has trailing newline\n      {\n        code: 'test\\n',\n        output: 'test',\n        options: ['never'],\n        errors: [{ messageId: 'mustNotEnd' }],\n      },\n      {\n        code: '<img>\\n',\n        output: '<img>',\n        options: ['never'],\n        errors: [{ messageId: 'mustNotEnd' }],\n      },\n      {\n        code: '{{#my-component}}\\n  test\\n{{/my-component}}\\n',\n        output: '{{#my-component}}\\n  test\\n{{/my-component}}',\n        options: ['never'],\n        errors: [{ messageId: 'mustNotEnd' }],\n      },\n    ],\n  });\n\n  //------------------------------------------------------------------------------\n  // EditorConfig integration tests\n  //------------------------------------------------------------------------------\n\n  describe('editorconfig option', () => {\n    const hbsRuleTesterEditorConfig = new RuleTester({\n      parser: require.resolve('ember-eslint-parser/hbs'),\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    });\n\n    afterEach(() => {\n      editorConfigUtil.resolveEditorConfig.mockReturnValue({});\n    });\n\n    describe('insert_final_newline: true behaves like always', () => {\n      beforeEach(() => {\n        editorConfigUtil.resolveEditorConfig.mockReturnValue({ insert_final_newline: true });\n      });\n\n      hbsRuleTesterEditorConfig.run('template-eol-last', rule, {\n        valid: [{ code: 'test\\n', options: ['editorconfig'] }],\n        invalid: [\n          {\n            code: 'test',\n            output: 'test\\n',\n            options: ['editorconfig'],\n            errors: [{ messageId: 'mustEnd' }],\n          },\n        ],\n      });\n    });\n\n    describe('insert_final_newline: false behaves like never', () => {\n      beforeEach(() => {\n        editorConfigUtil.resolveEditorConfig.mockReturnValue({ insert_final_newline: false });\n      });\n\n      hbsRuleTesterEditorConfig.run('template-eol-last', rule, {\n        valid: [{ code: 'test', options: ['editorconfig'] }],\n        invalid: [\n          {\n            code: 'test\\n',\n            output: 'test',\n            options: ['editorconfig'],\n            errors: [{ messageId: 'mustNotEnd' }],\n          },\n        ],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/template-linebreak-style.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst editorConfigUtil = require('../../../lib/utils/editorconfig');\nconst rule = require('../../../lib/rules/template-linebreak-style');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//\n// All tests are wrapped in a describe so beforeAll/afterAll can install a spy\n// on editorConfigUtil.resolveEditorConfig before any rule invocation. This\n// prevents the project's own .editorconfig from influencing test outcomes.\n//------------------------------------------------------------------------------\n\ndescribe('template-linebreak-style', () => {\n  beforeAll(() => {\n    vi.spyOn(editorConfigUtil, 'resolveEditorConfig').mockReturnValue({});\n  });\n\n  afterAll(() => {\n    vi.restoreAllMocks();\n  });\n\n  const ruleTester = new RuleTester({\n    parser: require.resolve('ember-eslint-parser'),\n    parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n  });\n\n  ruleTester.run('template-linebreak-style', rule, {\n    valid: [\n      // default 'unix' — LF only\n      '<template>\\n<div>test</div>\\n</template>',\n    ],\n\n    invalid: [\n      {\n        code: '<template>\\r\\n<div>test</div>\\r\\n</template>',\n        output: '<template>\\n<div>test</div>\\n</template>',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }, { messageId: 'wrongLinebreak' }],\n      },\n    ],\n  });\n\n  const hbsRuleTester = new RuleTester({\n    parser: require.resolve('ember-eslint-parser/hbs'),\n    parserOptions: {\n      ecmaVersion: 2022,\n      sourceType: 'module',\n    },\n  });\n\n  hbsRuleTester.run('template-linebreak-style', rule, {\n    valid: [\n      // default 'unix' — all LF\n      'testing this',\n      'testing \\n this',\n      { code: 'testing\\nthis', options: ['unix'] },\n      // windows — all CRLF\n      { code: 'testing\\r\\nthis', options: ['windows'] },\n      // no linebreaks at all\n      'single line no linebreaks',\n    ],\n\n    invalid: [\n      // default 'unix' — mixed linebreaks, first is LF so CRLF is wrong\n      {\n        code: 'something\\ngoes\\r\\n',\n        output: 'something\\ngoes\\n',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // unix — CRLF standalone\n      {\n        code: '\\r\\n',\n        output: '\\n',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // unix — CRLF in block mustache\n      {\n        code: '{{#if test}}\\r\\n{{/if}}',\n        output: '{{#if test}}\\n{{/if}}',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // unix — CRLF between mustaches\n      {\n        code: '{{blah}}\\r\\n{{blah}}',\n        output: '{{blah}}\\n{{blah}}',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // unix — CRLF trailing\n      {\n        code: '{{blah}}\\r\\n',\n        output: '{{blah}}\\n',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // unix — CRLF in attribute value\n      {\n        code: '{{blah arg=\"\\r\\n\"}}',\n        output: '{{blah arg=\"\\n\"}}',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // unix — CRLF in element attribute\n      {\n        code: '<blah arg=\"\\r\\n\" />',\n        output: '<blah arg=\"\\n\" />',\n        options: ['unix'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n      // windows — LF is wrong\n      {\n        code: '\\n',\n        output: '\\r\\n',\n        options: ['windows'],\n        errors: [{ messageId: 'wrongLinebreak' }],\n      },\n    ],\n  });\n\n  //------------------------------------------------------------------------------\n  // EditorConfig integration tests\n  //------------------------------------------------------------------------------\n\n  describe('editorconfig integration', () => {\n    const hbsRuleTesterEditorConfig = new RuleTester({\n      parser: require.resolve('ember-eslint-parser/hbs'),\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    });\n\n    afterEach(() => {\n      editorConfigUtil.resolveEditorConfig.mockReturnValue({});\n    });\n\n    describe('end_of_line: crlf overrides rule option', () => {\n      beforeEach(() => {\n        editorConfigUtil.resolveEditorConfig.mockReturnValue({ end_of_line: 'crlf' });\n      });\n\n      hbsRuleTesterEditorConfig.run('template-linebreak-style', rule, {\n        valid: [\n          // editorconfig crlf wins even when rule option says unix\n          { code: 'testing\\r\\nthis', options: ['unix'] },\n          // editorconfig crlf matches windows option too\n          { code: 'testing\\r\\nthis', options: ['windows'] },\n        ],\n        invalid: [\n          // LF is wrong when editorconfig says crlf\n          {\n            code: 'testing\\nthis',\n            output: 'testing\\r\\nthis',\n            options: ['unix'],\n            errors: [{ messageId: 'wrongLinebreak' }],\n          },\n        ],\n      });\n    });\n\n    describe('end_of_line: lf overrides rule option', () => {\n      beforeEach(() => {\n        editorConfigUtil.resolveEditorConfig.mockReturnValue({ end_of_line: 'lf' });\n      });\n\n      hbsRuleTesterEditorConfig.run('template-linebreak-style', rule, {\n        valid: [\n          // editorconfig lf wins even when rule option says windows\n          { code: 'testing\\nthis', options: ['windows'] },\n        ],\n        invalid: [\n          // CRLF is wrong when editorconfig says lf\n          {\n            code: 'testing\\r\\nthis',\n            output: 'testing\\nthis',\n            options: ['windows'],\n            errors: [{ messageId: 'wrongLinebreak' }],\n          },\n        ],\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/template-link-href-attributes.js",
    "content": "const rule = require('../../../lib/rules/template-link-href-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-link-href-attributes', rule, {\n  valid: [\n    '<template><a href=\"/about\">About</a></template>',\n    '<template><a href=\"https://example.com\">External</a></template>',\n    '<template><button>Click me</button></template>',\n    '<template><a role=\"link\" aria-disabled=\"true\">valid</a></template>',\n    '<template><a role=\"button\" aria-disabled=\"true\">valid</a></template>',\n    '<template><a href=\"\">Empty href</a></template>',\n    '<template><a href=\"#\">Hash href</a></template>',\n    '<template><a href={{this.link}}>Dynamic href</a></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><a>Link</a></template>',\n      output: null,\n      errors: [{ messageId: 'missingHref' }],\n    },\n    {\n      code: '<template><a onclick=\"doSomething()\">Click</a></template>',\n      output: null,\n      errors: [{ messageId: 'missingHref' }],\n    },\n    {\n      code: '<template><a role=\"button\">Action</a></template>',\n      output: null,\n      errors: [{ messageId: 'missingHref' }],\n    },\n    {\n      code: '<template><a aria-disabled=\"true\">Disabled only</a></template>',\n      output: null,\n      errors: [{ messageId: 'missingHref' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-link-rel-noopener.js",
    "content": "const rule = require('../../../lib/rules/template-link-rel-noopener');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-link-rel-noopener', rule, {\n  valid: [\n    '<template><a href=\"/\" target=\"_blank\" rel=\"noopener noreferrer\">Link</a></template>',\n    // reversed order\n    '<template><a href=\"/\" target=\"_blank\" rel=\"noreferrer noopener\">Link</a></template>',\n    // with additional values\n    '<template><a href=\"/\" target=\"_blank\" rel=\"nofollow noreferrer noopener\">Link</a></template>',\n    // no target=\"_blank\" means no rel required\n    '<template><a href=\"/\">Link</a></template>',\n    // target=\"_self\" does not require rel\n    '<template><a href=\"/some/where\" target=\"_self\"></a></template>',\n  ],\n  invalid: [\n    // no rel attribute at all\n    {\n      code: '<template><a href=\"/\" target=\"_blank\">Link</a></template>',\n      output: '<template><a href=\"/\" target=\"_blank\" rel=\"noopener noreferrer\">Link</a></template>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n    // rel=\"noopener\" only — missing noreferrer\n    {\n      code: '<template><a href=\"/\" target=\"_blank\" rel=\"noopener\">Link</a></template>',\n      output: '<template><a href=\"/\" target=\"_blank\" rel=\"noopener noreferrer\">Link</a></template>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n    // rel=\"noreferrer\" only — missing noopener\n    {\n      code: '<template><a href=\"/\" target=\"_blank\" rel=\"noreferrer\">Link</a></template>',\n      output: '<template><a href=\"/\" target=\"_blank\" rel=\"noopener noreferrer\">Link</a></template>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n    // rel=\"nofollow\" — present but wrong values\n    {\n      code: '<template><a href=\"/\" target=\"_blank\" rel=\"nofollow\">Link</a></template>',\n      output:\n        '<template><a href=\"/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Link</a></template>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-link-rel-noopener (hbs)', rule, {\n  valid: [\n    '<a href=\"/some/where\"></a>',\n    '<a href=\"/some/where\" target=\"_self\"></a>',\n    '<a href=\"/some/where\" target=\"_blank\" rel=\"noopener noreferrer\"></a>',\n    '<a href=\"/some/where\" target=\"_blank\" rel=\"noreferrer noopener\"></a>',\n    '<a href=\"/some/where\" target=\"_blank\" rel=\"nofollow noreferrer noopener\"></a>',\n  ],\n  invalid: [\n    {\n      code: '<a href=\"/some/where\" target=\"_blank\"></a>',\n      output: '<a href=\"/some/where\" target=\"_blank\" rel=\"noopener noreferrer\"></a>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n    {\n      code: '<a href=\"/some/where\" target=\"_blank\" rel=\"nofollow\"></a>',\n      output: '<a href=\"/some/where\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"></a>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n    {\n      code: '<a href=\"/some/where\" target=\"_blank\" rel=\"noopener\"></a>',\n      output: '<a href=\"/some/where\" target=\"_blank\" rel=\"noopener noreferrer\"></a>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n    {\n      code: '<a href=\"/some/where\" target=\"_blank\" rel=\"noreferrer\"></a>',\n      output: '<a href=\"/some/where\" target=\"_blank\" rel=\"noopener noreferrer\"></a>',\n      errors: [{ messageId: 'missingRel' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-modifier-name-case.js",
    "content": "const rule = require('../../../lib/rules/template-modifier-name-case');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-modifier-name-case', rule, {\n  // Rule is HBS-only: hyphenated identifiers are not valid JS, so camelCase\n  // modifier names in .gjs/.gts files are intentional and must not be flagged.\n  valid: [\n    '<template><div {{did-insert}}></div></template>',\n    '<template><div {{did-update}}></div></template>',\n    '<template><div {{on-click}}></div></template>',\n    '<template><div {{(modifier \"did-insert\")}}></div></template>',\n    '<template><div {{(modifier \"on-click\")}}></div></template>',\n    '<template><div {{did-insert \"something\"}}></div></template>',\n    '<template><div {{did-insert action=something}}></div></template>',\n    '<template><button {{on \"click\" somethingAmazing}}></button></template>',\n    '<template><button onclick={{do-a-thing \"foo\"}}></button></template>',\n    '<template><button onclick={{doAThing \"foo\"}}></button></template>',\n    '<template><a href=\"#\" onclick={{amazingActionThing \"foo\"}} {{did-insert}}></a></template>',\n    '<template><div didInsert></div></template>',\n    '<template><div {{(modifier \"foo-bar\")}}></div></template>',\n    '<template><div {{(if this.foo (modifier \"foo-bar\"))}}></div></template>',\n    '<template><div {{(modifier this.fooBar)}}></div></template>',\n    // camelCase modifiers in GJS are not flagged — hyphenated names are invalid JS identifiers\n    '<template><div {{didInsert}}></div></template>',\n    '<template><div {{doSomething}}></div></template>',\n    '<template><div {{fooBar}}></div></template>',\n    '<template><div {{FooBar}}></div></template>',\n    '<template><div {{(modifier \"fooBar\")}}></div></template>',\n  ],\n  invalid: [],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-modifier-name-case', rule, {\n  valid: [\n    { filename: 'test.hbs', code: '<div {{did-insert}}></div>' },\n    { filename: 'test.hbs', code: '<div {{did-insert \"something\"}}></div>' },\n    { filename: 'test.hbs', code: '<div {{did-insert action=something}}></div>' },\n    { filename: 'test.hbs', code: '<button {{on \"click\" somethingAmazing}}></button>' },\n    { filename: 'test.hbs', code: '<button onclick={{do-a-thing \"foo\"}}></button>' },\n    { filename: 'test.hbs', code: '<button onclick={{doAThing \"foo\"}}></button>' },\n    {\n      filename: 'test.hbs',\n      code: '<a href=\"#\" onclick={{amazingActionThing \"foo\"}} {{did-insert}}></a>',\n    },\n    { filename: 'test.hbs', code: '<div didInsert></div>' },\n    { filename: 'test.hbs', code: '<div {{(modifier \"foo-bar\")}}></div>' },\n    { filename: 'test.hbs', code: '<div {{(if this.foo (modifier \"foo-bar\"))}}></div>' },\n    { filename: 'test.hbs', code: '<div {{(modifier this.fooBar)}}></div>' },\n  ],\n  invalid: [\n    {\n      filename: 'test.hbs',\n      code: '<div {{didInsert}}></div>',\n      output: '<div {{did-insert}}></div>',\n      errors: [{ messageId: 'dasherized' }],\n    },\n    {\n      filename: 'test.hbs',\n      code: '<div class=\"monkey\" {{didInsert \"something\" with=\"somethingElse\"}}></div>',\n      output: '<div class=\"monkey\" {{did-insert \"something\" with=\"somethingElse\"}}></div>',\n      errors: [{ messageId: 'dasherized' }],\n    },\n    // PascalCase: index-0 guard prevents leading dash\n    {\n      filename: 'test.hbs',\n      code: '<div {{FooBar}}></div>',\n      output: '<div {{foo-bar}}></div>',\n      errors: [{ messageId: 'dasherized' }],\n    },\n    {\n      filename: 'test.hbs',\n      code: '<a href=\"#\" onclick={{amazingActionThing \"foo\"}} {{doSomething}}></a>',\n      output: '<a href=\"#\" onclick={{amazingActionThing \"foo\"}} {{do-something}}></a>',\n      errors: [{ messageId: 'dasherized' }],\n    },\n    {\n      filename: 'test.hbs',\n      code: '<div {{(modifier \"fooBar\")}}></div>',\n      output: '<div {{(modifier \"foo-bar\")}}></div>',\n      errors: [{ messageId: 'dasherized' }],\n    },\n    {\n      filename: 'test.hbs',\n      code: '<div {{(if this.foo (modifier \"fooBar\"))}}></div>',\n      output: '<div {{(if this.foo (modifier \"foo-bar\"))}}></div>',\n      errors: [{ messageId: 'dasherized' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-abstract-roles.js",
    "content": "const rule = require('../../../lib/rules/template-no-abstract-roles');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-abstract-roles', rule, {\n  valid: ['<template><div role=\"button\"></div></template>', '<template><div></div></template>'],\n  invalid: [\n    {\n      code: '<template><div role=\"command\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"widget\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"composite\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><input role=\"input\" /></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"landmark\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><input role=\"range\" /></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"roletype\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"section\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"sectionhead\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><select role=\"select\"></select></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"structure\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n    {\n      code: '<template><div role=\"window\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'abstractRole' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-accesskey-attribute.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-accesskey-attribute');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-accesskey-attribute', rule, {\n  valid: [\n    `<template>\n      <button>Click me</button>\n    </template>`,\n    `<template>\n      <div class=\"button\">Content</div>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <button accesskey=\"s\">Save</button>\n      </template>`,\n      output: `<template>\n        <button>Save</button>\n      </template>`,\n      errors: [\n        {\n          message:\n            'No access key attribute allowed. Inconsistencies between keyboard shortcuts and keyboard commands used by screenreader and keyboard only users create accessibility complications.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <a href=\"#\" accesskey=\"h\">Home</a>\n      </template>`,\n      output: `<template>\n        <a href=\"#\">Home</a>\n      </template>`,\n      errors: [\n        {\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    // Boolean attribute (no value)\n    {\n      code: '<template><button accesskey></button></template>',\n      output: '<template><button></button></template>',\n      errors: [{ messageId: 'noAccesskey' }],\n    },\n    // Dynamic mustache value\n    {\n      code: '<template><button accesskey={{someKey}}></button></template>',\n      output: '<template><button></button></template>',\n      errors: [{ messageId: 'noAccesskey' }],\n    },\n    // Concat string attribute\n    {\n      code: '<template><button accesskey=\"{{someKey}}\"></button></template>',\n      output: '<template><button></button></template>',\n      errors: [{ messageId: 'noAccesskey' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-action-modifiers.js",
    "content": "const rule = require('../../../lib/rules/template-no-action-modifiers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-action-modifiers', rule, {\n  valid: [\n    '<template><button {{on \"click\" this.handleClick}}>Click</button></template>',\n    '<template><div {{on \"mouseenter\" this.onHover}}>Hover</div></template>',\n    '<template><form {{on \"submit\" this.handleSubmit}}>Submit</form></template>',\n    '<template>{{action \"myAction\"}}</template>',\n    '<template>{{this.action}}</template>',\n    '<template>{{@action}}</template>',\n    {\n      code: '<template><button {{action \"save\"}}>Save</button></template>',\n      options: [{ allowlist: ['button'] }],\n    },\n    {\n      code: '<template><button {{action \"save\"}}>Save</button></template>',\n      options: [['button']],\n    },\n  ],\n\n  invalid: [\n    {\n      // String literal first param — no autofix\n      code: '<template><button {{action \"save\"}}>Save</button></template>',\n      output: null,\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      code: '<template><div {{action \"onClick\"}}>Click me</div></template>',\n      output: null,\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      code: '<template><form {{action \"submit\" on=\"submit\"}}>Submit</form></template>',\n      output: null,\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      // Path expression — autofix: {{action this.handleClick}} → {{on \"click\" this.handleClick}}\n      code: '<template><button {{action this.handleClick}}>Save</button></template>',\n      output: '<template><button {{on \"click\" this.handleClick}}>Save</button></template>',\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      // Path with args — autofix wraps in (fn ...)\n      code: '<template><button {{action this.handleClick \"arg1\"}}>Save</button></template>',\n      output:\n        '<template><button {{on \"click\" (fn this.handleClick \"arg1\")}}>Save</button></template>',\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      // Path with multiple args\n      code: '<template><button {{action this.handleClick \"arg1\" \"arg2\"}}>Save</button></template>',\n      output:\n        '<template><button {{on \"click\" (fn this.handleClick \"arg1\" \"arg2\")}}>Save</button></template>',\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      // Path expression with on=\"click\" hash — autofix reads event from hash and drops it\n      code: '<template><button {{action this.handleClick on=\"click\"}}>Save</button></template>',\n      output: '<template><button {{on \"click\" this.handleClick}}>Save</button></template>',\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      // Path expression with on=\"submit\" hash — autofix reads event from hash and drops it\n      code: '<template><form {{action this.handleSubmit on=\"submit\"}}>Submit</form></template>',\n      output: '<template><form {{on \"submit\" this.handleSubmit}}>Submit</form></template>',\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n    {\n      // Non-`on` hash pair present — no autofix (can't safely translate other hash pairs)\n      code: '<template><button {{action this.handleClick bubbles=false}}>Save</button></template>',\n      output: null,\n      errors: [{ messageId: 'noActionModifier' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-action-on-submit-button.js",
    "content": "const rule = require('../../../lib/rules/template-no-action-on-submit-button');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nconst ERROR_MESSAGE =\n  'In a `<form>`, a `<button>` with `type=\"submit\"` should have no click action';\n\nruleTester.run('template-no-action-on-submit-button', rule, {\n  valid: [\n    '<template>button</template>',\n    '<template><form><button type=\"button\" /></form></template>',\n    '<template><form><button type=\"button\" {{action this.handleClick}} /></form></template>',\n    '<template><form><button type=\"button\" {{action this.handleClick on=\"click\"}} /></form></template>',\n    '<template><form><button type=\"button\" {{action this.handleMouseover on=\"mouseOver\"}} /></form></template>',\n    '<template><form><button type=\"button\" {{on \"click\" this.handleClick}} /></form></template>',\n    '<template><form><button type=\"button\" {{on \"mouseover\" this.handleMouseover}} /></form></template>',\n    '<template>submit</template>',\n    '<template><form><button /></form></template>',\n    '<template><form><button type=\"submit\" /></form></template>',\n    '<template><form><button type=\"submit\" {{action this.handleMouseover on=\"mouseOver\"}} /></form></template>',\n    '<template><form><button type=\"submit\" {{on \"mouseover\" this.handleMouseover}} /></form></template>',\n    '<template><form><div/></form></template>',\n    '<template><form><div></div></form></template>',\n    '<template><form><div type=\"submit\"></div></form></template>',\n    '<template><form><div type=\"submit\" {{action this.handleClick}}></div></form></template>',\n    '<template><form><div type=\"submit\" {{on \"click\" this.handleClick}}></div></form></template>',\n    // <form method=\"dialog\"> — click handlers are intentional (closes dialog)\n    // https://github.com/ember-template-lint/ember-template-lint/issues/2989\n    '<template><form method=\"dialog\"><button type=\"submit\" {{on \"click\" this.handleClick}} /></form></template>',\n    '<template><form method=\"dialog\"><button {{action this.handleClick}} /></form></template>',\n    '<template><form method=\"DIALOG\"><button type=\"submit\" {{on \"click\" this.handleClick}} /></form></template>',\n    // Outside a form — valid\n    '<template><button {{action this.handleClick}} /></template>',\n    '<template><button {{action this.handleClick on=\"click\"}}/></template>',\n    '<template><button {{on \"click\" this.handleClick}} /></template>',\n    '<template><button type=\"submit\" {{action this.handleClick}} /></template>',\n    '<template><button type=\"submit\" {{action this.handleClick on=\"click\"}} /></template>',\n    '<template><button type=\"submit\" {{action (fn this.someAction \"foo\")}} /></template>',\n    '<template><button type=\"submit\" {{on \"click\" this.handleClick}} /></template>',\n    '<template><button type=\"submit\" {{on \"click\" (fn this.addNumber 123)}} /></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><form><button {{action this.handleClick}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button {{action this.handleClick on=\"click\"}}/></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button {{on \"click\" this.handleClick}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button type=\"submit\" {{action this.handleClick}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button type=\"submit\" {{action this.handleClick on=\"click\"}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button type=\"submit\" {{action (fn this.someAction \"foo\")}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button type=\"submit\" {{on \"click\" this.handleClick}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><button type=\"submit\" {{on \"click\" (fn this.addNumber 123)}} /></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><form><div><button type=\"submit\" {{action this.handleClick}} /></div></form></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-action-on-submit-button (hbs)', rule, {\n  valid: [\n    '<form><button type=\"button\" /></form>',\n    '<form><button type=\"button\" {{action this.handleClick}} /></form>',\n    '<form><button type=\"button\" {{action this.handleClick on=\"click\"}} /></form>',\n    '<form><button type=\"button\" {{action this.handleMouseover on=\"mouseOver\"}} /></form>',\n    '<form><button type=\"button\" {{on \"click\" this.handleClick}} /></form>',\n    '<form><button type=\"button\" {{on \"mouseover\" this.handleMouseover}} /></form>',\n    '<form><button /></form>',\n    '<form><button type=\"submit\" /></form>',\n    '<form><button type=\"submit\" {{action this.handleMouseover on=\"mouseOver\"}} /></form>',\n    '<form><button type=\"submit\" {{on \"mouseover\" this.handleMouseover}} /></form>',\n    '<form><div/></form>',\n    '<form><div></div></form>',\n    '<form><div type=\"submit\"></div></form>',\n    '<form><div type=\"submit\" {{action this.handleClick}}></div></form>',\n    '<form><div type=\"submit\" {{on \"click\" this.handleClick}}></div></form>',\n    // <form method=\"dialog\"> — click handlers are intentional\n    '<form method=\"dialog\"><button type=\"submit\" {{on \"click\" this.handleClick}} /></form>',\n    '<form method=\"dialog\"><button {{action this.handleClick}} /></form>',\n    // Outside a form — valid\n    '<button {{action this.handleClick}} />',\n    '<button {{action this.handleClick on=\"click\"}}/>',\n    '<button {{on \"click\" this.handleClick}} />',\n    '<button type=\"submit\" {{action this.handleClick}} />',\n    '<button type=\"submit\" {{action this.handleClick on=\"click\"}} />',\n    '<button type=\"submit\" {{action (fn this.someAction \"foo\")}} />',\n    '<button type=\"submit\" {{on \"click\" this.handleClick}} />',\n    '<button type=\"submit\" {{on \"click\" (fn this.addNumber 123)}} />',\n  ],\n  invalid: [\n    {\n      code: '<form><button {{action this.handleClick}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button {{action this.handleClick on=\"click\"}}/></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button {{on \"click\" this.handleClick}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button type=\"submit\" {{action this.handleClick}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button type=\"submit\" {{action this.handleClick on=\"click\"}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button type=\"submit\" {{action (fn this.someAction \"foo\")}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button type=\"submit\" {{on \"click\" this.handleClick}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><button type=\"submit\" {{on \"click\" (fn this.addNumber 123)}} /></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<form><div><button type=\"submit\" {{action this.handleClick}} /></div></form>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-action.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-action');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-action', rule, {\n  valid: [\n    `<template>\n      <button {{on \"click\" this.handleClick}}>Click</button>\n    </template>`,\n    `<template>\n      <button {{on \"click\" (fn this.save \"arg\")}}>Save</button>\n    </template>`,\n    `<template>\n      {{this.action}}\n    </template>`,\n    `<template>\n      {{@action}}\n    </template>`,\n\n    // GJS/GTS: a JS-scope binding shadows the ambient `action` keyword. The\n    // user has imported (or declared) their own `action`, so flagging would\n    // corrupt their reference.\n    {\n      filename: 'test.gjs',\n      code: \"import action from './my-action';\\n<template>{{action this.handleClick}}</template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import action from './my-action';\\n<template>{{(action this.handleClick)}}</template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import action from './my-action';\\n<template><button {{action this.handleClick}}>x</button></template>\",\n    },\n    {\n      filename: 'test.gts',\n      code: \"import action from './my-action';\\n<template>{{action this.handleClick}}</template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: 'const action = (h) => () => h();\\n<template>{{action this.handleClick}}</template>',\n    },\n\n    // Template block-param shadowing — `action` is the iterator/let-bound\n    // value, not the ambient keyword.\n    '<template>{{#each items as |action|}}{{action this.x}}{{/each}}</template>',\n    '<template>{{#let (component \"x\") as |action|}}{{action this.x}}{{/let}}</template>',\n    '<template>{{#each items as |action|}}<button {{action this.x}}>x</button>{{/each}}</template>',\n    '<template><Foo as |action|>{{action this.x}}</Foo></template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <button {{on \"click\" (action \"save\")}}>Save</button>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use `action` as (action ...) — deprecated in Ember 5.9, removed in 6.0. Use the `fn` helper instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{action \"doSomething\"}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use `action` in templates — deprecated in Ember 5.9, removed in 6.0. Use the `on` modifier and `fn` helper instead.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <button {{action \"submit\"}}>Submit</button>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use `action` as an element modifier — deprecated in Ember 5.9, removed in 6.0. Use the `on` modifier instead.',\n          type: 'GlimmerElementModifierStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input onclick={{action \"foo\"}}>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use `action` in templates — deprecated in Ember 5.9, removed in 6.0. Use the `on` modifier and `fn` helper instead.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n\n    // GJS/GTS: ambient `action` keyword usage with no shadowing import or\n    // block param. Still flagged.\n    {\n      filename: 'test.gjs',\n      code: '<template>{{action \"save\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'mustache', type: 'GlimmerMustacheStatement' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><button {{on \"click\" (action \"save\")}}>x</button></template>',\n      output: null,\n      errors: [{ messageId: 'subExpression', type: 'GlimmerSubExpression' }],\n    },\n    {\n      filename: 'test.gts',\n      code: '<template><button {{action \"submit\"}}>x</button></template>',\n      output: null,\n      errors: [{ messageId: 'modifier', type: 'GlimmerElementModifierStatement' }],\n    },\n\n    // Unrelated JS bindings do NOT mask the rule. Only a binding named\n    // `action` should be respected.\n    {\n      filename: 'test.gjs',\n      code: \"import handler from './handler';\\n<template>{{action this.x}}</template>\",\n      output: null,\n      errors: [{ messageId: 'mustache' }],\n    },\n\n    // Ambient `action` outside a block-param scope is still flagged, even\n    // when an inner block legitimately shadows it.\n    {\n      filename: 'test.gjs',\n      code: '<template>{{#each items as |action|}}{{action this.x}}{{/each}}{{action this.y}}</template>',\n      output: null,\n      errors: [{ messageId: 'mustache' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-args-paths.js",
    "content": "const rule = require('../../../lib/rules/template-no-args-paths');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-args-paths', rule, {\n  valid: [\n    '<template>{{@foo}}</template>',\n    // @args.foo is a valid named argument, not a path violation\n    '<template>{{@args.foo}}</template>',\n    '<template><div @foo={{cleanup this.args}}></div></template>',\n    '<template>{{foo (name this.args)}}</template>',\n    '<template>{{foo name=this.args}}</template>',\n    '<template>{{foo name=(extract this.args)}}</template>',\n    '<template><Foo @params={{this.args}} /></template>',\n    '<template><Foo {{mod this.args}} /></template>',\n    '<template><Foo {{mod items=this.args}} /></template>',\n    '<template><Foo {{mod items=(extract this.args)}} /></template>',\n    // args as a block param is not flagged\n    '<template>{{#each items as |args|}}{{args.name}}{{/each}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{hello (format value=args.foo)}}</template>',\n      output: '<template>{{hello (format value=@foo)}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template>{{hello value=args.foo}}</template>',\n      output: '<template>{{hello value=@foo}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template>{{hello (format args.foo.bar)}}</template>',\n      output: '<template>{{hello (format @foo.bar)}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template><br {{hello args.foo.bar}}></template>',\n      output: '<template><br {{hello @foo.bar}}></template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template>{{hello args.foo.bar}}</template>',\n      output: '<template>{{hello @foo.bar}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template>{{args.foo.bar}}</template>',\n      output: '<template>{{@foo.bar}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template>{{args.foo}}</template>',\n      output: '<template>{{@foo}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<template>{{this.args.foo}}</template>',\n      output: '<template>{{@foo}}</template>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-args-paths', rule, {\n  valid: [\n    // @args.foo is a valid named argument\n    '{{@args.foo}}',\n    '<div @foo={{cleanup this.args}}></div>',\n    '{{foo (name this.args)}}',\n    '{{foo name=this.args}}',\n    '{{foo name=(extract this.args)}}',\n    '<Foo @params={{this.args}} />',\n    '<Foo {{mod this.args}} />',\n    '<Foo {{mod items=this.args}} />',\n    '<Foo {{mod items=(extract this.args)}} />',\n    // args as a block param is not flagged\n    '{{#each items as |args|}}{{args.name}}{{/each}}',\n  ],\n  invalid: [\n    {\n      code: '{{hello (format value=args.foo)}}',\n      output: '{{hello (format value=@foo)}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '{{hello value=args.foo}}',\n      output: '{{hello value=@foo}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '{{hello (format args.foo.bar)}}',\n      output: '{{hello (format @foo.bar)}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '<br {{hello args.foo.bar}}>',\n      output: '<br {{hello @foo.bar}}>',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '{{hello args.foo.bar}}',\n      output: '{{hello @foo.bar}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '{{args.foo.bar}}',\n      output: '{{@foo.bar}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '{{args.foo}}',\n      output: '{{@foo}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n    {\n      code: '{{this.args.foo}}',\n      output: '{{@foo}}',\n      errors: [{ messageId: 'argsPath' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-arguments-for-html-elements.js",
    "content": "const rule = require('../../../lib/rules/template-no-arguments-for-html-elements');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-arguments-for-html-elements', rule, {\n  valid: [\n    '<template><div class=\"container\">Content</div></template>',\n    '<template><button type=\"submit\">Submit</button></template>',\n    '<template><MyComponent @title=\"Hello\" @onClick={{this.handler}} /></template>',\n    '<template><CustomButton @disabled={{true}} /></template>',\n    '<template><input value={{this.value}} /></template>',\n    // Custom elements aren't in the html-tags/svg-tags allowlists, so they're\n    // not flagged. Accepted false negative — web component namespace is open.\n    '<template><my-element @foo=\"x\" /></template>',\n    // Namespaced/path component invocations aren't in the allowlists either.\n    '<template><NS.Foo @bar=\"baz\" /></template>',\n    // Named blocks (colon-prefixed) aren't in the allowlists either.\n    '<template><Thing><:slot @item=\"x\">content</:slot></Thing></template>',\n    `let div = <template>{{@greeting}}</template>\n\n<template>\n  <div @greeting=\"hello\" />\n</template>`,\n  ],\n\n  invalid: [\n    {\n      code: '<template><div @title=\"Hello\">Content</div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@arguments can only be used on components, not HTML elements. Use regular attributes instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: '<template><button @onClick={{this.handler}}>Click</button></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@arguments can only be used on components, not HTML elements. Use regular attributes instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: '<template><span @data={{this.info}}>Text</span></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@arguments can only be used on components, not HTML elements. Use regular attributes instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      // SVG element — in svg-tags allowlist.\n      code: '<template><circle @r=\"5\" /></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@arguments can only be used on components, not HTML elements. Use regular attributes instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      // MathML element — in mathml-tag-names allowlist.\n      code: '<template><mfrac @numerator=\"x\" /></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@arguments can only be used on components, not HTML elements. Use regular attributes instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-aria-hidden-body.js",
    "content": "const rule = require('../../../lib/rules/template-no-aria-hidden-body');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-aria-hidden-body', rule, {\n  valid: [\n    '<template><body></body></template>',\n    '<template><div aria-hidden=\"true\"></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><body aria-hidden=\"true\"></body></template>',\n      output: '<template><body></body></template>',\n      errors: [{ messageId: 'noAriaHiddenBody' }],\n    },\n    {\n      code: '<template><body aria-hidden></body></template>',\n      output: '<template><body></body></template>',\n      errors: [{ messageId: 'noAriaHiddenBody' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-aria-unsupported-elements.js",
    "content": "const rule = require('../../../lib/rules/template-no-aria-unsupported-elements');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-aria-unsupported-elements', rule, {\n  valid: [\n    '<template><div role=\"button\" aria-label=\"Submit\"></div></template>',\n    '<template><button aria-pressed=\"true\">Toggle</button></template>',\n    '<template><input aria-label=\"Username\"></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><meta role=\"button\"></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><script aria-label=\"Script\"></script></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><style role=\"presentation\"></style></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n\n    {\n      code: '<template><col role=\"presentation\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><colgroup aria-label=\"x\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><noscript aria-hidden=\"true\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><picture aria-label=\"x\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><source aria-label=\"x\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n    {\n      code: '<template><track aria-label=\"x\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unsupported' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-array-prototype-extensions.js",
    "content": "const rule = require('../../../lib/rules/template-no-array-prototype-extensions');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-array-prototype-extensions', rule, {\n  valid: [\n    '<template>{{this.items.[0]}}</template>',\n    '<template>{{get this.items 0}}</template>',\n    '<template>{{this.users}}</template>',\n    '<template>{{@items}}</template>',\n    '<template>{{firstObject}}</template>',\n    '<template>{{length}}</template>',\n    // get helper with firstObject/lastObject as a direct top-level property (not an extension)\n    \"<template>{{get this 'firstObject'}}</template>\",\n    \"<template>{{get this 'lastObject.name'}}</template>\",\n    // Plain text nodes are not flagged\n    '<template>Just a regular text in the template bar.firstObject bar.lastObject.foo</template>',\n    // String-literal HTML attributes are not flagged\n    '<template><Foo foo=\"bar.firstObject.baz\" /></template>',\n  ],\n\n  invalid: [\n    // firstObject — basic path in MustacheStatement\n    {\n      code: '<template>{{this.items.firstObject}}</template>',\n      output: '<template>{{get this.items \"0\"}}</template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — path with trailing property\n    {\n      code: '<template>{{this.items.firstObject.name}}</template>',\n      output: '<template>{{get this.items \"0.name\"}}</template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — deeper path\n    {\n      code: '<template>{{this.model.items.firstObject}}</template>',\n      output: '<template>{{get this.model.items \"0\"}}</template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — in get helper string literal\n    {\n      code: '<template>{{get @model \"items.firstObject\"}}</template>',\n      output: '<template>{{get @model \"items.0\"}}</template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — in get helper string literal with trailing property\n    {\n      code: '<template>{{get @model \"items.firstObject.name\"}}</template>',\n      output: '<template>{{get @model \"items.0.name\"}}</template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — in hash argument context\n    {\n      code: '<template>{{foo bar=this.list.firstObject}}</template>',\n      output: '<template>{{foo bar=(get this.list \"0\")}}</template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — @arg prefix path\n    {\n      code: '<template><Foo @bar={{@list.firstObject}} /></template>',\n      output: '<template><Foo @bar={{get @list \"0\"}} /></template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — deeper path with trailing properties\n    {\n      code: '<template><Foo @bar={{this.list.firstObject.name.foo}} /></template>',\n      output: '<template><Foo @bar={{get this.list \"0.name.foo\"}} /></template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — subexpression param context\n    {\n      code: '<template><div data-test={{eq this.list.firstObject.abc \"def\"}}>Hello</div></template>',\n      output:\n        '<template><div data-test={{eq (get this.list \"0.abc\") \"def\"}}>Hello</div></template>',\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // lastObject — no fix available\n    {\n      code: '<template>{{this.users.lastObject}}</template>',\n      output: null,\n      errors: [{ messageId: 'lastObject' }],\n    },\n    // lastObject — deeper path, no fix\n    {\n      code: '<template>{{this.users.lastObject.name}}</template>',\n      output: null,\n      errors: [{ messageId: 'lastObject' }],\n    },\n    // lastObject — in get helper string literal, no fix\n    {\n      code: \"<template><Foo @bar={{get this 'list.lastObject'}} /></template>\",\n      output: null,\n      errors: [{ messageId: 'lastObject' }],\n    },\n    // firstObject — get helper with `this` as object and string literal path\n    {\n      code: \"<template><Foo @bar={{get this 'list.firstObject'}} /></template>\",\n      output: \"<template><Foo @bar={{get this 'list.0'}} /></template>\",\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // firstObject — get helper with @arg as object and firstObject at start of string path\n    {\n      code: \"<template><Foo @bar={{get @list 'firstObject.name'}} /></template>\",\n      output: \"<template><Foo @bar={{get @list '0.name'}} /></template>\",\n      errors: [{ messageId: 'firstObject' }],\n    },\n    // lastObject — in named hash argument\n    {\n      code: '<template>{{foo bar=@list.lastObject.test}}</template>',\n      output: null,\n      errors: [{ messageId: 'lastObject' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-at-ember-render-modifiers.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-at-ember-render-modifiers');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-at-ember-render-modifiers', rule, {\n  valid: [\n    `<template>\n      <div></div>\n    </template>`,\n    `<template>\n      <div {{on \"click\" this.handleClick}}></div>\n    </template>`,\n    `<template>\n      <MyComponent />\n    </template>`,\n\n    '<template><div {{this.someModifier}}></div></template>',\n    '<template><div {{someModifier}}></div></template>',\n    '<template><div {{did-foo}}></div></template>',\n    '<template>{{did-insert}}</template>',\n    '<template>{{did-update}}</template>',\n    '<template>{{will-destroy}}</template>',\n\n    // In GJS/GTS, kebab identifiers cannot be imports; these are bare paths\n    // that happen to share the canonical name but are not the render modifier.\n    {\n      filename: 'test.gjs',\n      code: '<template><div {{did-insert this.setup}}></div></template>',\n    },\n    // Unrelated imports with matching local names should not match\n    {\n      filename: 'test.gjs',\n      code: `import didInsert from './my-lib';\n        <template><div {{didInsert this.setup}}></div></template>`,\n    },\n    // Root-package import of an unknown named export is not a render modifier\n    {\n      filename: 'test.gjs',\n      code: `import { somethingElse } from '@ember/render-modifiers';\n        <template><div {{somethingElse this.setup}}></div></template>`,\n    },\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <div {{did-insert this.setup}}></div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          messageId: 'noRenderModifier',\n          type: 'GlimmerElementModifierStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div {{did-update this.update}}></div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          messageId: 'noRenderModifier',\n          type: 'GlimmerElementModifierStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div {{will-destroy this.cleanup}}></div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          messageId: 'noRenderModifier',\n          type: 'GlimmerElementModifierStatement',\n        },\n      ],\n    },\n\n    {\n      code: '<template><div {{did-insert}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      code: '<template><div {{did-update}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      code: '<template><div {{will-destroy}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n\n    // GJS/GTS import-based forms — local name is user-chosen\n    {\n      filename: 'test.gjs',\n      code: `import didInsert from '@ember/render-modifiers/modifiers/did-insert';\n        <template><div {{didInsert this.setup}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: `import didUpdate from '@ember/render-modifiers/modifiers/did-update';\n        <template><div {{didUpdate this.update}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: `import willDestroy from '@ember/render-modifiers/modifiers/will-destroy';\n        <template><div {{willDestroy this.cleanup}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    // Renamed default import still flags\n    {\n      filename: 'test.gjs',\n      code: `import myInsert from '@ember/render-modifiers/modifiers/did-insert';\n        <template><div {{myInsert this.setup}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n\n    // Root-package named imports — all three modifiers\n    {\n      filename: 'test.gjs',\n      code: `import { didInsert } from '@ember/render-modifiers';\n        <template><div {{didInsert this.setup}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: `import { didUpdate } from '@ember/render-modifiers';\n        <template><div {{didUpdate this.update}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: `import { willDestroy } from '@ember/render-modifiers';\n        <template><div {{willDestroy this.cleanup}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    // Aliased root-package import still flags\n    {\n      filename: 'test.gjs',\n      code: `import { didInsert as myModifier } from '@ember/render-modifiers';\n        <template><div {{myModifier this.setup}}></div></template>`,\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-at-ember-render-modifiers (hbs)', rule, {\n  valid: [\n    '<div {{this.someModifier}}></div>',\n    '<div {{someModifier}}></div>',\n    '<div {{did-foo}}></div>',\n    '{{did-insert}}',\n    '{{did-update}}',\n    '{{will-destroy}}',\n  ],\n  invalid: [\n    {\n      code: '<div {{did-insert}}></div>',\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      code: '<div {{did-update}}></div>',\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n    {\n      code: '<div {{will-destroy}}></div>',\n      output: null,\n      errors: [{ messageId: 'noRenderModifier' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-attrs-in-components.js",
    "content": "const rule = require('../../../lib/rules/template-no-attrs-in-components');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-attrs-in-components', rule, {\n  valid: [\n    // Not a component template path: nothing is flagged, regardless of content.\n    {\n      filename: 'app/templates/application.hbs',\n      code: '<template>{{@value}}</template>',\n    },\n    {\n      filename: 'app/templates/application.hbs',\n      code: '<template>{{this.value}}</template>',\n    },\n    // `this.attrs.*` outside a component template — not flagged (path gate).\n    {\n      filename: 'app/templates/application.hbs',\n      code: '<template>{{this.attrs.foo}}</template>',\n    },\n    // Even `attrs.*` itself is only flagged inside component templates.\n    {\n      filename: 'app/templates/application.hbs',\n      code: '<template>{{attrs.value}}</template>',\n    },\n    // Inside a component template, non-attrs paths are fine.\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{@value}}</template>',\n    },\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{this.value}}</template>',\n    },\n    // Pod-style components path matches the gate, but no `attrs` usage.\n    {\n      filename: 'app/components/foo/template.hbs',\n      code: '<template>{{@value}}</template>',\n    },\n    // `-components/` path gate, no `attrs` usage.\n    {\n      filename: 'app/ui-components/foo.hbs',\n      code: '<template>{{@value}}</template>',\n    },\n    // Octane co-located template — non-attrs path is fine.\n    {\n      filename: 'app/components/foo.hbs',\n      code: '<template>{{@value}}</template>',\n    },\n  ],\n  invalid: [\n    // Bare `attrs.*` inside `templates/components/` — flagged.\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{attrs.foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // Bare `attrs` (no dotted tail) inside `templates/components/` — flagged.\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{attrs}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // Pod-style path `components/*/template` — flagged.\n    {\n      filename: 'app/components/foo/template.hbs',\n      code: '<template>{{attrs.name}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // `ui/components` path — flagged.\n    {\n      filename: 'app/ui/components/foo.hbs',\n      code: '<template>{{attrs.name}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // `-components/` path — flagged.\n    {\n      filename: 'app/ui-components/foo.hbs',\n      code: '<template>{{attrs.name}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // `this.attrs.*` inside a component template — flagged (real @ember/component API).\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{this.attrs.foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // attrs in attribute value position.\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template><div class={{attrs.foo}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // attrs in block helper condition.\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{#if attrs.foo}}bar{{/if}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // attrs as hash pair value.\n    {\n      filename: 'app/templates/components/foo.hbs',\n      code: '<template>{{bar foo=attrs.foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n    // Octane co-located template (app/components/foo.hbs) — flagged.\n    {\n      filename: 'app/components/foo.hbs',\n      code: '<template>{{attrs.foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'noAttrs' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-autofocus-attribute.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-autofocus-attribute');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-autofocus-attribute', rule, {\n  valid: [\n    `<template>\n      <input type=\"text\" />\n    </template>`,\n    `<template>\n      <button>Click me</button>\n    </template>`,\n    // Mustache boolean-literal forms render NO attribute when the literal\n    // is false — these are the statically-known opt-outs that align with\n    // HTML boolean-attribute semantics.\n    `<template>\n      <input autofocus={{false}} />\n    </template>`,\n    `<template>\n      {{input autofocus=false}}\n    </template>`,\n    // Dialog exception (MDN): autofocus on <dialog> is recommended.\n    `<template>\n      <dialog autofocus></dialog>\n    </template>`,\n    // Dialog descendants are also exempt (angular-eslint parity).\n    `<template>\n      <dialog>\n        <button autofocus>Close</button>\n      </dialog>\n    </template>`,\n    `<template>\n      <dialog>\n        <div>\n          <input autofocus />\n        </div>\n      </dialog>\n    </template>`,\n    // Dialog exception also applies to the classic mustache form\n    // (`{{input autofocus=true}}`) — whether direct child or nested.\n    `<template>\n      <dialog>\n        {{input autofocus=true}}\n      </dialog>\n    </template>`,\n    `<template>\n      <dialog>\n        <div>\n          {{input autofocus=true}}\n        </div>\n      </dialog>\n    </template>`,\n\n    // Custom helpers / components taking an `autofocus` prop are opaque —\n    // we can't know whether the prop forwards to a native <input autofocus>\n    // or is used for something else. Narrow to {{input}} / {{component\n    // \"input\"}} which deterministically render native inputs.\n    '<template>{{my-wrapper autofocus=true}}</template>',\n    '<template>{{some-component autofocus=true name=\"foo\"}}</template>',\n    '<template>{{component \"some-other-helper\" autofocus=true}}</template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <input type=\"text\" autofocus />\n      </template>`,\n      output: `<template>\n        <input type=\"text\"/>\n      </template>`,\n      errors: [\n        {\n          message:\n            'Avoid using autofocus attribute. Autofocusing elements can cause usability issues for sighted and non-sighted users.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <textarea autofocus></textarea>\n      </template>`,\n      output: `<template>\n        <textarea></textarea>\n      </template>`,\n      errors: [\n        {\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{input type=\"text\" autofocus=true}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerHashPair',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{component \"input\" type=\"text\" autofocus=true}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerHashPair',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div autofocus>\n        </div>\n      </template>`,\n      output: `<template>\n        <div>\n        </div>\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <h1 autofocus>\n        </h1>\n      </template>`,\n      output: `<template>\n        <h1>\n        </h1>\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input autofocus=\"autofocus\" />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    // Value-aware: truthy literals and any dynamic value still flag.\n    {\n      code: `<template>\n        <input autofocus=\"true\" />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input autofocus={{true}} />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input autofocus={{\"true\"}} />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input autofocus={{this.shouldFocus}} />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    // Dialog exception only applies within <dialog>; siblings elsewhere still flag.\n    {\n      code: `<template>\n        <section>\n          <button autofocus>Focus</button>\n        </section>\n      </template>`,\n      output: `<template>\n        <section>\n          <button>Focus</button>\n        </section>\n      </template>`,\n      errors: [\n        {\n          messageId: 'noAutofocus',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n\n    // Per HTML boolean-attribute semantics, the string \"false\" / mustache\n    // string \"false\" / hash-pair string \"false\" are all TRUTHY. Only the\n    // mustache boolean-literal {{false}} renders no attribute.\n    {\n      code: `<template>\n        <input autofocus=\"false\" />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [{ messageId: 'noAutofocus', type: 'GlimmerAttrNode' }],\n    },\n    {\n      code: `<template>\n        <input autofocus={{\"false\"}} />\n      </template>`,\n      output: `<template>\n        <input />\n      </template>`,\n      errors: [{ messageId: 'noAutofocus', type: 'GlimmerAttrNode' }],\n    },\n    {\n      code: `<template>\n        {{input autofocus=\"false\"}}\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'noAutofocus' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-bare-strings.js",
    "content": "const rule = require('../../../lib/rules/template-no-bare-strings');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-bare-strings', rule, {\n  valid: [\n    '<template>{{t \"hello.world\"}}</template>',\n    '<template><div>&amp;</div></template>',\n    '<template><div>   </div></template>',\n    {\n      code: '<template><div>Welcome</div></template>',\n      options: [{ allowlist: ['Welcome'] }],\n    },\n\n    '<template><div class={{if true \"disabled\"}}></div></template>',\n    '<template><div class={{unless false \"disabled\"}}></div></template>',\n    '<template><div class={{concat \"disabled\"}}></div></template>',\n    '<template><div class={{unless true \"asd\"}}></div></template>',\n    '<template><div class={{unless @a @b}}></div></template>',\n    '<template>{{unless @a @b}}</template>',\n    '<template>{{t \"howdy\"}}</template>',\n    '<template><CustomInput @type={{\"range\"}} /></template>',\n    '<template>{{t \"foo\"}}</template>',\n    '<template>{{t \"foo\"}}, {{t \"bar\"}} ({{length}})</template>',\n    '<template>(),.&+-=*/#%!?:[]{}</template>',\n    '<template>&lpar;&rpar;&comma;&period;&amp;&nbsp;</template>',\n    '<template>&mdash;&ndash;</template>',\n    '<template><script> fdff sf sf f </script></template>',\n    '<template><style> fdff sf sf f </style></template>',\n    '<template><pre> fdff sf sf f </pre></template>',\n    '<template><script> fdff sf sf <div> aaa </div> f </script></template>',\n    '<template><style> fdff sf sf <div> aaa </div> f </style></template>',\n    '<template><pre> fdff sf sf <div> aaa </div> f </pre></template>',\n    '<template><textarea> this is an input</textarea></template>',\n    '<template><div placeholder=\"wat?\"></div></template>',\n    // In GJS/GTS (strict mode), Input/Textarea could be custom components —\n    // not checked to avoid false positives, matching ember-template-lint behavior.\n    {\n      filename: 'template.gjs',\n      code: '<template><Input placeholder=\"This is a placeholder\" /></template>',\n    },\n    {\n      filename: 'template.gjs',\n      code: '<template><Textarea placeholder=\"This is a placeholder\" /></template>',\n    },\n    {\n      filename: 'template.gjs',\n      code: '<template><Input @placeholder=\"This is a placeholder\" /></template>',\n    },\n    {\n      filename: 'template.gts',\n      code: '<template><Textarea @placeholder=\"This is a placeholder\" /></template>',\n    },\n    `<template><foo-bar>\n</foo-bar></template>`,\n    '<template><div data-test-foo-bar></div></template>',\n    '<template>{{page-title}}</template>',\n    '<template>{{page-title (t \"foo\")}}</template>',\n    '<template>{{page-title @model.foo}}</template>',\n    '<template>{{page-title this.model.foo}}</template>',\n    '<template>{{page-title this.model.foo \" - \" this.model.bar}}</template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><div>Hello World</div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><button>Click me</button></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><p>Some text content here</p></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n\n    {\n      code: '<template>{{unless true \"asd\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template>{{unless @b \"b\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div>{{concat \"foo\" \"bar\"}}</div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div>{{unless true \"Yes\" \"No\"}}</div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }, { messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div>{{if true \"Yes\" \"No\"}}</div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }, { messageId: 'bareString' }],\n    },\n    {\n      code: '<template><p>{{\"Hello!\"}}</p></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: `<template>\n howdy</template>`,\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: `<template><div>\n  1234\n</div></template>`,\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><a title=\"hahaha trolol\"></a></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><input placeholder=\"trolol\"></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><input placeholder=\"{{foo}}hahaha trolol\"></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><Input placeholder=\"{{foo}}hahaha trolol\" /></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><Textarea placeholder=\"{{foo}}hahaha trolol\" /></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><Input @placeholder=\"{{foo}}hahaha trolol\" /></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><Textarea @placeholder=\"{{foo}}hahaha trolol\" /></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div role=\"contentinfo\" aria-label=\"Contact, Policies and Legal\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div contenteditable role=\"searchbox\" aria-placeholder=\"Search for things\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div role=\"region\" aria-roledescription=\"slide\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template><div role=\"slider\" aria-valuetext=\"Off\" tabindex=\"0\" aria-valuemin=\"0\" aria-valuenow=\"0\" aria-valuemax=\"3\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: `<template><div>Bady\n  <input placeholder=\"trolol\">\n</div></template>`,\n      output: null,\n      errors: [{ messageId: 'bareString' }, { messageId: 'bareString' }],\n    },\n    {\n      code: '<template>{{page-title \"foo\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      code: '<template>{{page-title \"foo\" \" - \" \"bar\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }, { messageId: 'bareString' }],\n    },\n    {\n      filename: 'template.gjs',\n      code: '<template><input placeholder=\"This is a placeholder\" /></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n    {\n      filename: 'template.gts',\n      code: '<template><img alt=\"This is alt text\" /></template>',\n      output: null,\n      errors: [{ messageId: 'bareString' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-bare-strings', rule, {\n  valid: [\n    '<div class={{if true \"disabled\"}}></div>',\n    '<div class={{unless false \"disabled\"}}></div>',\n    '<div class={{concat \"disabled\"}}></div>',\n    '<div class={{unless true \"asd\"}}></div>',\n    '<div class={{unless @a @b}}></div>',\n    '{{unless @a @b}}',\n    '{{t \"howdy\"}}',\n    '<CustomInput @type={{\"range\"}} />',\n    '{{t \"foo\"}}',\n    '{{t \"foo\"}}, {{t \"bar\"}} ({{length}})',\n    '(),.&+-=*/#%!?:[]{}',\n    '&lpar;&rpar;&comma;&period;&amp;&nbsp;',\n    '&mdash;&ndash;',\n    '{{! template-lint-disable no-bare-strings }}',\n    '{{! template-lint-disable }}',\n    '<script> fdff sf sf f </script>',\n    '<style> fdff sf sf f </style>',\n    '<pre> fdff sf sf f </pre>',\n    '<script> fdff sf sf <div> aaa </div> f </script>',\n    '<style> fdff sf sf <div> aaa </div> f </style>',\n    '<pre> fdff sf sf <div> aaa </div> f </pre>',\n    '<template> fdff sf sf f </template>',\n    '<template> fdff sf sf <div> aaa </div> f </template>',\n    '<template><input placeholder=\"This is a placeholder\" /></template>',\n    '<template><img alt=\"This is alt text\" /></template>',\n    '<textarea> this is an input</textarea>',\n    '<div placeholder=\"wat?\"></div>',\n    `<foo-bar>\n</foo-bar>`,\n    '<div data-test-foo-bar></div>',\n    '{{page-title}}',\n    '{{page-title (t \"foo\")}}',\n    '{{page-title @model.foo}}',\n    '{{page-title this.model.foo}}',\n    '{{page-title this.model.foo \" - \" this.model.bar}}',\n    '&nbsp;',\n    `\n {{translate \"greeting\"}}`,\n    `\n {{translate \"greeting\"}},`,\n    '& &times;',\n    '<img data-alt={{bar}}>',\n    '<div data-foo={{foo}}></div>',\n    '<template><input placeholder={{t \"placeholder\"}} /></template>',\n    '<template><img alt={{t \"alt text\"}} /></template>',\n    // Custom allowlist options.\n    {\n      code: '&nbsp;',\n      options: [{ allowlist: [';'] }],\n    },\n    {\n      code: '&nbsp;',\n      options: [[';']],\n    },\n    {\n      code: '\\nfoo',\n      options: [['foo']],\n    },\n    {\n      code: 'tarzan!\\t\\n  tarzan!',\n      options: [['tarzan!']],\n    },\n    {\n      code: '4 &times; 3=12',\n      options: [['&', '&times;', '4', '3=12']],\n    },\n    {\n      code: 'Tom & Jerry',\n      options: [['&', '&times;', 'Tom', 'Jerry']],\n    },\n    {\n      code: 'howdy',\n      options: [{ allowlist: ['howdy'] }],\n    },\n    {\n      code: '\\u20B9',\n      options: [['\\u20B9']],\n    },\n    {\n      code: '&#8377;',\n      options: [['&#8377;']],\n    },\n    {\n      code: '{{t \"foo\"}} / \"{{name}}\"',\n      options: [['/', '\"']],\n    },\n    // Custom ignoredElements.\n    {\n      code: '<mj-style>some style</mj-style>',\n      options: [{ ignoredElements: ['mj-style'] }],\n    },\n    // Allowlist covers attribute content.\n    {\n      code: '<input placeholder=\"{{foo}}X\">',\n      options: [['X']],\n    },\n  ],\n  invalid: [\n    {\n      code: '{{unless true \"asd\"}}',\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: '{{unless @b \"b\"}}',\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: '<div>{{concat \"foo\" \"bar\"}}</div>',\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: '<div>{{unless true \"Yes\" \"No\"}}</div>',\n      output: null,\n      errors: [\n        { message: 'Non-translated string used' },\n        { message: 'Non-translated string used' },\n      ],\n    },\n    {\n      code: '<div>{{if true \"Yes\" \"No\"}}</div>',\n      output: null,\n      errors: [\n        { message: 'Non-translated string used' },\n        { message: 'Non-translated string used' },\n      ],\n    },\n    {\n      code: '<p>{{\"Hello!\"}}</p>',\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: `\n howdy`,\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: `<div>\n  1234\n</div>`,\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: '<a title=\"hahaha trolol\"></a>',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `title` attribute' }],\n    },\n    {\n      code: '<input placeholder=\"trolol\">',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `placeholder` attribute' }],\n    },\n    {\n      code: '<input placeholder=\"{{foo}}hahaha trolol\">',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `placeholder` attribute' }],\n    },\n    {\n      code: '<Input placeholder=\"{{foo}}hahaha trolol\" />',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `placeholder` attribute' }],\n    },\n    {\n      code: '<Textarea placeholder=\"{{foo}}hahaha trolol\" />',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `placeholder` attribute' }],\n    },\n    {\n      code: '<Input @placeholder=\"{{foo}}hahaha trolol\" />',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `@placeholder` argument' }],\n    },\n    {\n      code: '<Textarea @placeholder=\"{{foo}}hahaha trolol\" />',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `@placeholder` argument' }],\n    },\n    {\n      code: '<div role=\"contentinfo\" aria-label=\"Contact, Policies and Legal\"></div>',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `aria-label` attribute' }],\n    },\n    {\n      code: '<div contenteditable role=\"searchbox\" aria-placeholder=\"Search for things\"></div>',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `aria-placeholder` attribute' }],\n    },\n    {\n      code: '<div role=\"region\" aria-roledescription=\"slide\"></div>',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `aria-roledescription` attribute' }],\n    },\n    {\n      code: '<div role=\"slider\" aria-valuetext=\"Off\" tabindex=\"0\" aria-valuemin=\"0\" aria-valuenow=\"0\" aria-valuemax=\"3\"></div>',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `aria-valuetext` attribute' }],\n    },\n    {\n      code: `<div>Bady\n  <input placeholder=\"trolol\">\n</div>`,\n      output: null,\n      errors: [\n        { message: 'Non-translated string used' },\n        { message: 'Non-translated string used in `placeholder` attribute' },\n      ],\n    },\n    {\n      code: '{{page-title \"foo\"}}',\n      output: null,\n      errors: [{ message: 'Non-translated string used' }],\n    },\n    {\n      code: '{{page-title \"foo\" \" - \" \"bar\"}}',\n      output: null,\n      errors: [\n        { message: 'Non-translated string used' },\n        { message: 'Non-translated string used' },\n      ],\n    },\n    {\n      code: '{{t \"foo\"}} / error / &lpar;\"{{name}}\"&rpar;',\n      output: null,\n      errors: [\n        { message: 'Non-translated string used' },\n        { message: 'Non-translated string used' },\n      ],\n    },\n    {\n      code: '<input placeholder=\"hahaha\">',\n      output: null,\n      errors: [{ message: 'Non-translated string used in `placeholder` attribute' }],\n    },\n    // Custom globalAttributes config.\n    {\n      code: '<div data-foo=\"derpy\"></div>',\n      output: null,\n      options: [{ globalAttributes: ['data-foo'] }],\n      errors: [{ message: 'Non-translated string used in `data-foo` attribute' }],\n    },\n    // Custom elementAttributes config.\n    {\n      code: '<img data-alt=\"some alternate here\">',\n      output: null,\n      options: [{ elementAttributes: { img: ['data-alt'] } }],\n      errors: [{ message: 'Non-translated string used in `data-alt` attribute' }],\n    },\n    // Custom allowlist — only non-allowlisted text triggers error.\n    {\n      code: '{{t \"foo\"}} / error / &lpar;\"{{name}}\"&rpar;',\n      output: null,\n      options: [{ allowlist: ['/', '\"'] }],\n      errors: [{ message: 'Non-translated string used' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-bare-yield.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-bare-yield');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-bare-yield', rule, {\n  valid: [\n    // yield with params is fine\n    '<template>{{yield this}}</template>',\n    '<template>{{yield @model}}</template>',\n    '<template>{{yield (hash someProp=someValue)}}</template>',\n    // yield is not the only content\n    '<template>{{yield}}<div>Content</div></template>',\n    '<template><div>Content</div>{{yield}}</template>',\n    // no yield at all\n    '<template><div>Content</div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{yield}}</template>',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n    {\n      // whitespace around yield doesn't count as other content\n      code: '<template>  {{yield}}  </template>',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n    {\n      // comments don't count as other content\n      code: '<template>{{! a comment }}{{yield}}</template>',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-bare-yield', rule, {\n  valid: [\n    '{{yield (hash someProp=someValue)}}',\n    '{{yield this}}',\n    // yield with other content\n    '{{yield}}<div>Content</div>',\n  ],\n  invalid: [\n    {\n      code: '{{yield}}',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n    {\n      // whitespace around yield doesn't count as other content\n      code: '     {{yield}}',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n    {\n      code: '\\n  {{yield}}\\n     ',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n    {\n      // comments don't count as other content\n      code: '\\n{{! some comment }}  {{yield}}\\n     ',\n      output: null,\n      errors: [{ messageId: 'noBareYield' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-block-params-for-html-elements.js",
    "content": "const rule = require('../../../lib/rules/template-no-block-params-for-html-elements');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-block-params-for-html-elements', rule, {\n  valid: [\n    `let div = <template>{{yield \"hello\"}}</template>;\n    <template>\n      <div as |greeting|>{{greeting}}</div>\n    </template>\n    `,\n    '<template><div>Content</div></template>',\n    '<template><MyComponent as |item|>{{item.name}}</MyComponent></template>',\n    '<template>{{#each this.items as |item|}}<li>{{item}}</li>{{/each}}</template>',\n    '<template><button>Click</button></template>',\n    // Custom elements aren't in the html-tags/svg-tags allowlists, so they're\n    // not flagged. Accepted false negative — web component namespace is open.\n    '<template><my-element as |x|>{{x}}</my-element></template>',\n    // Namespaced/path component invocations aren't in the allowlists either.\n    '<template><NS.Foo as |x|>{{x}}</NS.Foo></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><div as |content|>{{content}}</div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Block params can only be used with components, not HTML elements.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: '<template><section as |data|><p>{{data}}</p></section></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Block params can only be used with components, not HTML elements.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: '<template><ul as |items|><li>{{items}}</li></ul></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Block params can only be used with components, not HTML elements.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      // SVG element — in svg-tags allowlist.\n      code: '<template><circle as |r|>{{r}}</circle></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Block params can only be used with components, not HTML elements.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      // MathML element — in mathml-tag-names allowlist.\n      code: '<template><mfrac as |num|>{{num}}</mfrac></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Block params can only be used with components, not HTML elements.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-builtin-form-components.js",
    "content": "const rule = require('../../../lib/rules/template-no-builtin-form-components');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-builtin-form-components', rule, {\n  valid: [\n    // Native HTML elements are always fine\n    { filename: 'test.gjs', code: '<template><input type=\"text\" /></template>' },\n    { filename: 'test.gjs', code: '<template><input type=\"checkbox\" /></template>' },\n    { filename: 'test.gjs', code: '<template><input type=\"radio\" /></template>' },\n    { filename: 'test.gjs', code: '<template><textarea></textarea></template>' },\n    { filename: 'test.gjs', code: '<template><div></div></template>' },\n\n    // In GJS without an import from @ember/component, <Input>/<Textarea> are not the builtins\n    { filename: 'test.gjs', code: '<template><Input /></template>' },\n    { filename: 'test.gjs', code: '<template><Textarea></Textarea></template>' },\n\n    // Importing from a different source is fine\n    {\n      filename: 'test.gjs',\n      code: \"import { Input } from './my-components'; <template><Input /></template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { Textarea } from './my-components'; <template><Textarea></Textarea></template>\",\n    },\n  ],\n  invalid: [\n    {\n      filename: 'test.gjs',\n      code: \"import { Input } from '@ember/component'; <template><Input /></template>\",\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: 'import { Input } from \\'@ember/component\\'; <template><Input type=\"text\" /></template>',\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      // Aliased import must still be flagged\n      filename: 'test.gjs',\n      code: \"import { Input as EmberInput } from '@ember/component'; <template><EmberInput /></template>\",\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { Textarea } from '@ember/component'; <template><Textarea></Textarea></template>\",\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { Textarea } from '@ember/component'; <template><Textarea @value={{this.body}}></Textarea></template>\",\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    {\n      // Aliased Textarea import must still be flagged\n      filename: 'test.gjs',\n      code: \"import { Textarea as EmberTextarea } from '@ember/component'; <template><EmberTextarea></EmberTextarea></template>\",\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    // Yielded as a value\n    {\n      filename: 'test.gjs',\n      code: \"import { Input } from '@ember/component'; <template>{{yield Input}}</template>\",\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { Input as EmberInput } from '@ember/component'; <template>{{yield EmberInput}}</template>\",\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { Textarea } from '@ember/component'; <template>{{yield Textarea}}</template>\",\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    // Used in helpers / passed as argument\n    {\n      filename: 'test.gjs',\n      code: \"import { Input } from '@ember/component'; <template><MyForm @field={{Input}} /></template>\",\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { Input } from '@ember/component'; <template>{{component Input}}</template>\",\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-builtin-form-components (hbs)', rule, {\n  valid: [\n    '<input type=\"text\" />',\n    '<input type=\"checkbox\" />',\n    '<input type=\"radio\" />',\n    '<textarea></textarea>',\n  ],\n  invalid: [\n    {\n      code: '<Input />',\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      code: '<Input type=\"text\" />',\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      code: '<Textarea></Textarea>',\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    {\n      code: '<Textarea @value={{this.body}}></Textarea>',\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    // Yielded as a value\n    {\n      code: '{{yield Input}}',\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      code: '{{yield Textarea}}',\n      output: null,\n      errors: [{ messageId: 'noTextarea' }],\n    },\n    // Used in helpers / passed as argument\n    {\n      code: '<MyForm @field={{Input}} />',\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n    {\n      code: '{{component Input}}',\n      output: null,\n      errors: [{ messageId: 'noInput' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-capital-arguments.js",
    "content": "const { RuleTester } = require('eslint');\nconst rule = require('../../../lib/rules/template-no-capital-arguments');\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-capital-arguments', rule, {\n  valid: [\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@arg}}</div>\n          </template>\n        }\n      `,\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@myArgument}}</div>\n          </template>\n        }\n      `,\n    },\n    // @name in attribute position (lowercase) is valid\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Foo @name=\"bar\" />\n          </template>\n        }\n      `,\n    },\n    // Nested lowercase path is valid\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@arg.nested}}</div>\n          </template>\n        }\n      `,\n    },\n  ],\n\n  invalid: [\n    // Capital path expression\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@Arg}}</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'noCapitalArguments' }],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@MyArgument}}</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'noCapitalArguments' }],\n    },\n    // Capital attr node\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Foo @Name=\"bar\" />\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'noCapitalArguments' }],\n    },\n    // Nested capital path\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@Arg.nested}}</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'noCapitalArguments' }],\n    },\n    // Underscore prefix\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@_Name}}</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'noCapitalArguments' }],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Foo @_ame=\"bar\" />\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'noCapitalArguments' }],\n    },\n    // Reserved arguments in path expression\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@arguments}}</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'reservedArgument' }],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>{{@args}}</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'reservedArgument' }],\n    },\n    // Reserved arguments in attr position\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Foo @arguments={{42}} />\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'reservedArgument' }],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Foo @block={{42}} />\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'reservedArgument' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-chained-this.js",
    "content": "const rule = require('../../../lib/rules/template-no-chained-this');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-chained-this', rule, {\n  valid: [\n    '<template>{{this.value}}</template>',\n    '<template>{{this.thisvalue}}</template>',\n    '<template>{{@argName}}</template>',\n    '<template>{{this.user.name}}</template>',\n    '<template><this.Component /></template>',\n    '<template>{{component this.dynamicComponent}}</template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template>{{this.this.value}}</template>',\n      output: '<template>{{this.value}}</template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n    {\n      code: '<template>{{helper value=this.this.foo}}</template>',\n      output: '<template>{{helper value=this.foo}}</template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n    {\n      code: '<template>{{#if this.this.condition}}true{{/if}}</template>',\n      output: '<template>{{#if this.condition}}true{{/if}}</template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n    {\n      code: '<template><this.this.Component /></template>',\n      output: '<template><this.Component /></template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n    {\n      code: '<template>{{#this.this.value}}woo{{/this.this.value}}</template>',\n      output: '<template>{{#this.value}}woo{{/this.value}}</template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n    {\n      code: '<template>{{component this.this.dynamicComponent}}</template>',\n      output: '<template>{{component this.dynamicComponent}}</template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n    {\n      code: '<template><this.this.Component>content</this.this.Component></template>',\n      output: '<template><this.Component>content</this.Component></template>',\n      errors: [{ messageId: 'noChainedThis' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-class-bindings.js",
    "content": "const rule = require('../../../lib/rules/template-no-class-bindings');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-class-bindings', rule, {\n  valid: [\n    '<template><SomeThing /></template>',\n    '<template>{{lol-wat}}</template>',\n    '<template>{{true}}</template>',\n    '<template>{{\"hehe\"}}</template>',\n    '<template><div class=\"foo\"></div></template>',\n    // Rule is HBS-only: @classBinding in GJS/GTS may be a legitimate component argument\n    {\n      filename: 'test.gjs',\n      code: '<template><SomeThing @classBinding=\"lol:wat\" /></template>',\n    },\n    {\n      filename: 'test.gts',\n      code: '<template>{{some-thing classNameBindings=\"lol:foo:bar\"}}</template>',\n    },\n  ],\n  invalid: [\n    {\n      code: '<template>{{some-thing classBinding=\"lol:wat\"}}</template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noClassBindings',\n          data: { name: 'classBinding' },\n        },\n      ],\n    },\n    {\n      code: '<template><SomeThing @classBinding=\"lol:wat\" /></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noClassBindings',\n          data: { name: '@classBinding' },\n        },\n      ],\n    },\n    {\n      code: '<template>{{some-thing classNameBindings=\"lol:foo:bar\"}}</template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noClassBindings',\n          data: { name: 'classNameBindings' },\n        },\n      ],\n    },\n    {\n      code: '<template><SomeThing @classNameBindings=\"lol:foo:bar\" /></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noClassBindings',\n          data: { name: '@classNameBindings' },\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-class-bindings', rule, {\n  valid: ['<SomeThing />', '{{lol-wat}}', '{{true}}', '{{\"hehe\"}}'],\n  invalid: [\n    {\n      code: '{{some-thing classBinding=\"lol:wat\"}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Passing the `classBinding` property as an argument within templates is not allowed.',\n        },\n      ],\n    },\n    {\n      code: '<SomeThing @classBinding=\"lol:wat\" />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Passing the `@classBinding` property as an argument within templates is not allowed.',\n        },\n      ],\n    },\n    {\n      code: '{{some-thing classNameBindings=\"lol:foo:bar\"}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Passing the `classNameBindings` property as an argument within templates is not allowed.',\n        },\n      ],\n    },\n    {\n      code: '<SomeThing @classNameBindings=\"lol:foo:bar\" />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Passing the `@classNameBindings` property as an argument within templates is not allowed.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-curly-component-invocation.js",
    "content": "const rule = require('../../../lib/rules/template-no-curly-component-invocation');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nfunction generateError(name) {\n  const parts = name.split('/');\n  const angleBracketName = parts\n    .map((part) => {\n      return part\n        .split('-')\n        .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n        .join('');\n    })\n    .join('::');\n  return `You are using the component {{${name}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \\`'no-curly-component-invocation': { allow: ['${name}'] }\\`.`;\n}\n\nruleTester.run('template-no-curly-component-invocation', rule, {\n  valid: [\n    // With noImplicitThis:true (default), explicit this/@ paths are always valid\n    '<template>{{this.foo}}</template>',\n    '<template>{{@bar}}</template>',\n    '<template>{{42}}</template>',\n    '<template>{{true}}</template>',\n    '<template>{{foo bar}}</template>',\n    '<template>{{#each items as |item|}}{{item}}{{/each}}</template>',\n    // ElementNode block params should be recognized as local variables\n    '<template><Foo as |bar|>{{bar}}</Foo></template>',\n    '<template><Foo as |bar|>{{bar.baz}}</Foo></template>',\n    '<template>{{#if someProperty}}yay{{/if}}</template>',\n    '<template><FooBar /></template>',\n    {\n      // {{foo}} is not flagged when noImplicitThis is disabled\n      code: '<template>{{foo}}</template>',\n      options: [{ noImplicitThis: false }],\n    },\n    {\n      // {{foo.bar}} is not flagged when noImplicitThis is disabled\n      code: '<template>{{foo.bar}}</template>',\n      options: [{ noImplicitThis: false }],\n    },\n    {\n      code: '<template>{{foo-bar}}</template>',\n      options: [{ allow: ['foo-bar'] }],\n    },\n\n    // GJS/GTS: JS scope bindings (imports, const) used as curly invocations\n    // are explicit by name. Converting to <Foo> would reference an unbound\n    // identifier, so skip both single-word and named-args forms.\n    `import fooBar from './foo-bar';\n     export default <template>{{fooBar}}</template>;`,\n    `import fooBar from './foo-bar';\n     export default <template>{{fooBar arg=1}}</template>;`,\n    `import fooBar from './foo-bar';\n     export default <template>{{#fooBar}}content{{/fooBar}}</template>;`,\n    `const someHelper = () => 'x';\n     export default <template>{{someHelper}}</template>;`,\n  ],\n  invalid: [\n    {\n      code: '<template>{{foo-bar}}</template>',\n      output: null,\n      errors: [\n        {\n          message: generateError('foo-bar'),\n        },\n      ],\n    },\n    {\n      code: '<template>{{nested/component}}</template>',\n      output: null,\n      errors: [\n        {\n          message: generateError('nested/component'),\n        },\n      ],\n    },\n    {\n      code: '<template>{{#foo-bar}}content{{/foo-bar}}</template>',\n      output: '<template><FooBar>content</FooBar></template>',\n      errors: [\n        {\n          message:\n            \"You are using the component {{#foo-bar}} with curly component syntax. You should use <FooBar> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. `'no-curly-component-invocation': { allow: ['foo-bar'] }`.\",\n        },\n      ],\n    },\n    {\n      code: '<template>{{#foo-bar}}{{/foo-bar}}</template>',\n      output: '<template><FooBar></FooBar></template>',\n      errors: [\n        {\n          message:\n            \"You are using the component {{#foo-bar}} with curly component syntax. You should use <FooBar> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. `'no-curly-component-invocation': { allow: ['foo-bar'] }`.\",\n        },\n      ],\n    },\n    {\n      code: '<template>{{#foo-bar/baz/boo-foo}}block{{/foo-bar/baz/boo-foo}}</template>',\n      output: '<template><FooBar::Baz::BooFoo>block</FooBar::Baz::BooFoo></template>',\n      errors: [\n        {\n          message:\n            \"You are using the component {{#foo-bar/baz/boo-foo}} with curly component syntax. You should use <FooBar::Baz::BooFoo> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. `'no-curly-component-invocation': { allow: ['foo-bar/baz/boo-foo'] }`.\",\n        },\n      ],\n    },\n    {\n      code: '<template>{{#some-component foo=\"bar\"}}foo{{/some-component}}</template>',\n      output: '<template><SomeComponent @foo=\"bar\">foo</SomeComponent></template>',\n      errors: [\n        {\n          message:\n            \"You are using the component {{#some-component}} with curly component syntax. You should use <SomeComponent> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. `'no-curly-component-invocation': { allow: ['some-component'] }`.\",\n        },\n      ],\n    },\n    {\n      // noImplicitThis:true (default) flags plain single-word names\n      code: '<template>{{foo}}</template>',\n      output: null,\n      errors: [{ message: generateError('foo') }],\n    },\n    {\n      // noImplicitThis:true (default) flags multi-part paths\n      code: '<template>{{foo.bar}}</template>',\n      output: null,\n      errors: [{ message: generateError('foo.bar') }],\n    },\n    {\n      code: '<template>{{foo}}</template>',\n      output: null,\n      options: [{ disallow: ['foo'] }],\n      errors: [\n        {\n          message: generateError('foo'),\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nfunction generateBlockError(name, isLocal) {\n  let angleBracketName;\n  if (name.startsWith('@') || name.startsWith('this.') || isLocal) {\n    angleBracketName = name;\n  } else {\n    const parts = name.split('/');\n    angleBracketName = parts\n      .map((part) => {\n        return part\n          .split('-')\n          .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n          .join('');\n      })\n      .join('::');\n  }\n  return `You are using the component {{#${name}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \\`'no-curly-component-invocation': { allow: ['${name}'] }\\`.`;\n}\n\nhbsRuleTester.run('template-no-curly-component-invocation', rule, {\n  valid: [\n    // Plain single-word / multi-part paths are valid when noImplicitThis is disabled\n    {\n      code: '{{foo}}',\n      options: [{ noImplicitThis: false }],\n    },\n    {\n      code: '{{foo.bar}}',\n      options: [{ noImplicitThis: false }],\n    },\n    {\n      code: '{{model.selectedTransfersCount}}',\n      options: [{ noImplicitThis: false }],\n    },\n    {\n      code: '{{request.note}}',\n      options: [{ noImplicitThis: false }],\n    },\n    // Built-in helpers / keywords (always valid)\n    '{{#each items as |item|}}{{item}}{{/each}}',\n    '{{#each items as |item|}}{{item.foo}}{{/each}}',\n    // ElementNode block params should be recognized as local variables\n    '<Foo as |bar|>{{bar}}</Foo>',\n    '<Foo as |bar|>{{bar.baz}}</Foo>',\n    '{{42}}',\n    '{{true}}',\n    '{{undefined}}',\n    '{{\"foo-bar\"}}',\n    '{{foo bar}}',\n    '<div {{foo}} />',\n    '<Foo @bar={{baz}} />',\n    '{{#foo bar}}{{/foo}}',\n    '{{#foo}}bar{{else}}baz{{/foo}}',\n    '{{array}}',\n    '{{concat}}',\n    '{{debugger}}',\n    '{{has-block}}',\n    '{{has-block-params}}',\n    '{{hasBlock}}',\n    '{{hash}}',\n    '{{outlet}}',\n    '{{unique-id}}',\n    '{{yield}}',\n    '{{yield to=\"inverse\"}}',\n    '{{app-version}}',\n    '{{app-version versionOnly=true}}',\n    '<GoodCode />',\n    '<GoodCode></GoodCode>',\n    '{{if someProperty \"yay\"}}',\n    '<Nested::GoodCode />',\n    '<Nested::GoodCode @someProperty={{-50}} @someProperty=\"-50\" @someProperty={{true}} />',\n    '{{some-valid-helper param}}',\n    '{{some/valid-nested-helper param}}',\n    // Explicit this/@ paths are always valid\n    '{{@someArg}}',\n    '{{this.someProperty}}',\n    '{{#-in-element destinationElement}}Hello{{/-in-element}}',\n    '{{#in-element destinationElement}}Hello{{/in-element}}',\n    '{{#some-component foo=\"bar\"}}foo{{else}}bar{{/some-component}}',\n    '<MyComponent @arg={{my-helper this.foobar}} />',\n    '<MyComponent @arg=\"{{my-helper this.foobar}}\" />',\n    '<MyComponent {{my-modifier this.foobar}} />',\n    '{{svg-jar \"status\"}}',\n    '{{t \"some.translation.key\"}}',\n    '{{#animated-if condition}}foo{{/animated-if}}',\n    // Allow config\n    {\n      code: '{{aaa-bbb}}',\n      options: [{ allow: ['aaa-bbb', 'aaa/bbb'] }],\n    },\n    {\n      code: '{{aaa/bbb}}',\n      options: [{ allow: ['aaa-bbb', 'aaa/bbb'] }],\n    },\n    {\n      code: '{{aaa-bbb bar=baz}}',\n      options: [{ allow: ['aaa-bbb', 'aaa/bbb'] }],\n    },\n    {\n      code: '{{#aaa-bbb bar=baz}}{{/aaa-bbb}}',\n      options: [{ allow: ['aaa-bbb', 'aaa/bbb'] }],\n    },\n    // noImplicitThis: block params exempt {{item}} even with noImplicitThis:true\n    {\n      code: '{{#each items as |item|}}{{item}}{{/each}}',\n      options: [{ noImplicitThis: true }],\n    },\n    // noImplicitThis: block params exempt multi-part access too\n    {\n      code: '{{#each items as |item|}}{{item.name}}{{/each}}',\n      options: [{ noImplicitThis: true }],\n    },\n    // noImplicitThis: explicit this./ @ paths never flagged\n    {\n      code: '{{this.someProperty}}',\n      options: [{ noImplicitThis: true }],\n    },\n    {\n      code: '{{@someArg}}',\n      options: [{ noImplicitThis: true }],\n    },\n    // disallow: block params exempt the disallowed name\n    {\n      code: '{{#each items as |disallowed|}}{{disallowed}}{{/each}}',\n      options: [{ disallow: ['disallowed'], noImplicitThis: false }],\n    },\n    // requireDash: true — single-word names with named args are not flagged (not obviously a component)\n    {\n      code: '{{foo bar=baz}}',\n      options: [{ requireDash: true }],\n    },\n  ],\n  invalid: [\n    {\n      code: '{{foo-bar}}',\n      output: null,\n      errors: [{ message: generateError('foo-bar') }],\n    },\n    {\n      code: '{{nested/component}}',\n      output: null,\n      errors: [{ message: generateError('nested/component') }],\n    },\n    {\n      code: '{{#foo-bar}}{{/foo-bar}}',\n      output: '<FooBar></FooBar>',\n      errors: [{ message: generateBlockError('foo-bar') }],\n    },\n    {\n      code: '{{#foo-bar as |foo-baz|}}{{foo-baz}}{{/foo-bar}}',\n      output: '<FooBar as |foo-baz|>{{foo-baz}}</FooBar>',\n      errors: [{ message: generateBlockError('foo-bar') }, { message: generateError('foo-baz') }],\n    },\n    {\n      code: '{{#foo-bar as |foo-baz|}}{{#foo-baz as |foo-boo|}}{{foo-boo}}{{/foo-baz}}{{/foo-bar}}',\n      output: '<FooBar as |foo-baz|>{{#foo-baz as |foo-boo|}}{{foo-boo}}{{/foo-baz}}</FooBar>',\n      errors: [\n        { message: generateBlockError('foo-bar') },\n        { message: generateBlockError('foo-baz', true) },\n        { message: generateError('foo-boo') },\n      ],\n    },\n    {\n      code: '{{#foo-bar as |foo-baz|}}{{foos-baz}}{{/foo-bar}}',\n      output: '<FooBar as |foo-baz|>{{foos-baz}}</FooBar>',\n      errors: [{ message: generateBlockError('foo-bar') }, { message: generateError('foos-baz') }],\n    },\n    {\n      code: '{{#this.foo-bar as |foo-baz|}}{{foos-baz}}{{/this.foo-bar}}',\n      output: '<this.foo-bar as |foo-baz|>{{foos-baz}}</this.foo-bar>',\n      errors: [\n        { message: generateBlockError('this.foo-bar') },\n        { message: generateError('foos-baz') },\n      ],\n    },\n    {\n      code: '{{#this.fooBar as |foo-baz|}}{{foos-baz}}{{/this.fooBar}}',\n      output: '<this.fooBar as |foo-baz|>{{foos-baz}}</this.fooBar>',\n      errors: [\n        { message: generateBlockError('this.fooBar') },\n        { message: generateError('foos-baz') },\n      ],\n    },\n    {\n      code: '{{#@foo-bar as |foo-baz|}}{{foos-baz}}{{/@foo-bar}}',\n      output: '<@foo-bar as |foo-baz|>{{foos-baz}}</@foo-bar>',\n      errors: [{ message: generateBlockError('@foo-bar') }, { message: generateError('foos-baz') }],\n    },\n    {\n      code: '{{#@fooBar as |foo-baz|}}{{foos-baz}}{{/@fooBar}}',\n      output: '<@fooBar as |foo-baz|>{{foos-baz}}</@fooBar>',\n      errors: [{ message: generateBlockError('@fooBar') }, { message: generateError('foos-baz') }],\n    },\n    {\n      code: '{{#let (component \"foo\") as |my-component|}}{{#my-component}}{{/my-component}}{{/let}}',\n      output: '{{#let (component \"foo\") as |my-component|}}<my-component></my-component>{{/let}}',\n      errors: [{ message: generateBlockError('my-component', true) }],\n    },\n    // Curly component invocations with hash params\n    {\n      code: '{{foo-bar bar=baz}}',\n      output: null,\n      errors: [{ message: generateError('foo-bar') }],\n    },\n    // Multi-part path with named args is always flagged ({{foo.bar bar=baz}})\n    {\n      code: '{{foo.bar bar=baz}}',\n      output: null,\n      errors: [{ message: generateError('foo.bar') }],\n    },\n    // link-to with positional params\n    {\n      code: '{{link-to \"bar\" \"foo\"}}',\n      output: null,\n      errors: [{ message: generateError('link-to') }],\n    },\n    // block link-to with positional params\n    {\n      code: '{{#link-to \"foo\"}}bar{{/link-to}}',\n      output: null,\n      errors: [{ message: generateBlockError('link-to') }],\n    },\n    // input with hash params\n    {\n      code: '{{input type=\"text\" value=this.model.name}}',\n      output: null,\n      errors: [{ message: generateError('input') }],\n    },\n    // textarea with hash params\n    {\n      code: '{{textarea value=this.model.body}}',\n      output: null,\n      errors: [{ message: generateError('textarea') }],\n    },\n    // Disallow config\n    {\n      code: '{{disallowed}}',\n      output: null,\n      options: [{ disallow: ['disallowed'] }],\n      errors: [{ message: generateError('disallowed') }],\n    },\n    // noImplicitThis: plain single-word name flagged with noImplicitThis:true\n    {\n      code: '{{foo}}',\n      output: null,\n      options: [{ noImplicitThis: true }],\n      errors: [{ message: generateError('foo') }],\n    },\n    // noImplicitThis: multi-part path flagged with noImplicitThis:true\n    {\n      code: '{{foo.bar}}',\n      output: null,\n      options: [{ noImplicitThis: true }],\n      errors: [{ message: generateError('foo.bar') }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-debugger.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-debugger');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-debugger', rule, {\n  valid: [\n    `<template>\n      <div>Hello World</div>\n    </template>`,\n    `<template>\n      {{this.debug}}\n    </template>`,\n    `<template>\n      {{debugMode}}\n    </template>`,\n    `<template>\n      <div data-test-debugger={{true}}></div>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        {{debugger}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected debugger statement in template.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{#if condition}}\n          {{debugger}}\n        {{/if}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected debugger statement in template.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{#debugger}}\n          content\n        {{/debugger}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected debugger statement in template.',\n          type: 'GlimmerBlockStatement',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-deprecated.js",
    "content": "'use strict';\n\nconst path = require('node:path');\nconst rule = require('../../../lib/rules/template-no-deprecated');\nconst RuleTester = require('eslint').RuleTester;\n\nconst FIXTURES_DIR = path.join(__dirname, '../rules-preprocessor/template-no-deprecated');\n\n// Block 1: No TypeScript project -- rule is a no-op\n// When parserServices.program is absent, the rule returns {} and never reports.\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-deprecated', rule, {\n  valid: [\n    // Non-deprecated component reference\n    \"import SomeComponent from './some-component';\\n<template><SomeComponent /></template>\",\n    // Plain HTML tag -- never reported\n    '<template><div></div></template>',\n    // this.something -- not a scope reference\n    '<template>{{this.foo}}</template>',\n    // Undefined reference -- no def, skip\n    '<template>{{undefinedThing}}</template>',\n  ],\n  invalid: [],\n});\n\n// Block 2: TypeScript project -- full deprecation checking\n//\n// Unlike most rule tests, this block requires physical fixture files in\n// tests/lib/rules-preprocessor/template-no-deprecated/. Two reasons:\n//\n// 1. The tsconfig uses glob patterns to build its file list. The `filename`\n//    passed to RuleTester must physically exist so TypeScript includes it.\n//\n// 2. This rule only checks ImportBinding definitions. To detect @deprecated,\n//    TypeScript must resolve the import and read the JSDoc from the source\n//    file. Inline class/function definitions are not checked.\n//\n// Rules that don't use parserOptions.project, or whose logic doesn't depend\n// on TypeScript import resolution, can use any virtual filename.\n\nconst PREPROCESSOR_DIR = path.join(__dirname, '../rules-preprocessor');\n\nconst ruleTesterTyped = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: {\n    project: path.join(PREPROCESSOR_DIR, 'tsconfig.eslint.json'),\n    tsconfigRootDir: PREPROCESSOR_DIR,\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    extraFileExtensions: ['.gts'],\n  },\n});\n\nruleTesterTyped.run('template-no-deprecated (with TS project)', rule, {\n  valid: [\n    // Non-deprecated component\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import CurrentComponent from './current-component';\\n<template><CurrentComponent /></template>\",\n    },\n    // Plain HTML tag\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: '<template><div></div></template>',\n    },\n    // this.something — no scope reference\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: '<template>{{this.foo}}</template>',\n    },\n    // Non-deprecated @arg\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import ComponentWithArgs from './component-with-args';\\n<template><ComponentWithArgs @newArg='x' /></template>\",\n    },\n    // @arg on a component with no typed Args\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import CurrentComponent from './current-component';\\n<template><CurrentComponent @anyArg='x' /></template>\",\n    },\n  ],\n  invalid: [\n    // Deprecated component in element position\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import DeprecatedComponent from './deprecated-component';\\n<template><DeprecatedComponent /></template>\",\n      output: null,\n      errors: [{ messageId: 'deprecatedWithReason', type: 'GlimmerElementNodePart' }],\n    },\n    // Deprecated helper in mustache position\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import { deprecatedHelper } from './deprecated-helper';\\n<template>{{deprecatedHelper}}</template>\",\n      output: null,\n      errors: [{ messageId: 'deprecated', type: 'VarHead' }],\n    },\n    // Deprecated helper in sub-expression position\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import { deprecatedHelper } from './deprecated-helper';\\n<template>{{fn (deprecatedHelper)}}</template>\",\n      output: null,\n      errors: [{ messageId: 'deprecated', type: 'VarHead' }],\n    },\n    // Deprecated component in block position\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import DeprecatedComponent from './deprecated-component';\\n<template>{{#DeprecatedComponent}}{{/DeprecatedComponent}}</template>\",\n      output: null,\n      errors: [{ messageId: 'deprecatedWithReason', type: 'VarHead' }],\n    },\n    // Deprecated @arg with reason\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import ComponentWithArgs from './component-with-args';\\n<template><ComponentWithArgs @oldArg='x' /></template>\",\n      output: null,\n      errors: [\n        {\n          messageId: 'deprecatedWithReason',\n          data: { name: '@oldArg', reason: 'use newArg instead' },\n        },\n      ],\n    },\n    // Deprecated @arg without reason\n    {\n      filename: path.join(FIXTURES_DIR, 'usage.gts'),\n      code: \"import ComponentWithArgs from './component-with-args';\\n<template><ComponentWithArgs @oldArgNoReason='x' /></template>\",\n      output: null,\n      errors: [{ messageId: 'deprecated', data: { name: '@oldArgNoReason' } }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-duplicate-attributes.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-duplicate-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-duplicate-attributes', rule, {\n  valid: [\n    `<template>\n      <div class=\"foo\" id=\"bar\"></div>\n    </template>`,\n    `<template>\n      <button type=\"button\" disabled></button>\n    </template>`,\n    `<template>\n      {{helper arg1=\"a\" arg2=\"b\"}}\n    </template>`,\n    `<template>\n      {{#each items as |item|}}\n        {{item}}\n      {{/each}}\n    </template>`,\n\n    '<template>{{my-component firstName=firstName lastName=lastName}}</template>',\n    '<template> {{fullName}}</template>',\n    '<template><a class=\"btn\">{{btnLabel}}</a></template>',\n    '<template>{{employee-profile employee=(hash firstName=firstName lastName=lastName age=age)}}</template>',\n    '<template>{{employee-profile employee=(hash fullName=(hash firstName=firstName lastName=lastName) age=age)}}</template>',\n\n    // Block form with params (no duplicates)\n    '<template>{{#my-component firstName=firstName lastName=lastName as |fullName|}}{{fullName}}{{/my-component}}</template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <div class=\"foo\" class=\"bar\"></div>\n      </template>`,\n      output: `<template>\n        <div class=\"foo\"></div>\n      </template>`,\n      errors: [\n        {\n          message: \"Duplicate attribute 'class' found in the Element.\",\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input type=\"text\" disabled type=\"email\" />\n      </template>`,\n      output: `<template>\n        <input type=\"text\" disabled />\n      </template>`,\n      errors: [\n        {\n          message: \"Duplicate attribute 'type' found in the Element.\",\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{helper foo=\"bar\" foo=\"baz\"}}\n      </template>`,\n      output: `<template>\n        {{helper foo=\"bar\"}}\n      </template>`,\n      errors: [\n        {\n          message: \"Duplicate attribute 'foo' found in the MustacheStatement.\",\n          type: 'GlimmerHashPair',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{#if condition key=\"a\" key=\"b\"}}\n          content\n        {{/if}}\n      </template>`,\n      output: `<template>\n        {{#if condition key=\"a\"}}\n          content\n        {{/if}}\n      </template>`,\n      errors: [\n        {\n          message: \"Duplicate attribute 'key' found in the BlockStatement.\",\n          type: 'GlimmerHashPair',\n        },\n      ],\n    },\n\n    {\n      code: '<template>{{my-component firstName=firstName lastName=lastName firstName=firstName}}</template>',\n      output: '<template>{{my-component firstName=firstName lastName=lastName}}</template>',\n      errors: [{ messageId: 'duplicateMustache', data: { name: 'firstName' } }],\n    },\n    {\n      code: '<template>{{#my-component firstName=firstName lastName=lastName firstName=firstName as |fullName|}}{{/my-component}}</template>',\n      output:\n        '<template>{{#my-component firstName=firstName lastName=lastName as |fullName|}}{{/my-component}}</template>',\n      errors: [{ messageId: 'duplicateBlock', data: { name: 'firstName' } }],\n    },\n    {\n      code: '<template><a class=\"btn\" class=\"btn\">{{btnLabel}}</a></template>',\n      output: '<template><a class=\"btn\">{{btnLabel}}</a></template>',\n      errors: [{ messageId: 'duplicateElement', data: { name: 'class' } }],\n    },\n    {\n      code: '<template>{{employee-profile employee=(hash firstName=firstName lastName=lastName age=age firstName=firstName)}}</template>',\n      output:\n        '<template>{{employee-profile employee=(hash firstName=firstName lastName=lastName age=age)}}</template>',\n      errors: [{ messageId: 'duplicateSubExpr', data: { name: 'firstName' } }],\n    },\n    {\n      code: '<template>{{employee-profile employee=(hash fullName=(hash firstName=firstName lastName=lastName firstName=firstName) age=age)}}</template>',\n      output:\n        '<template>{{employee-profile employee=(hash fullName=(hash firstName=firstName lastName=lastName) age=age)}}</template>',\n      errors: [{ messageId: 'duplicateSubExpr', data: { name: 'firstName' } }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-duplicate-attributes (hbs)', rule, {\n  valid: [\n    '{{my-component firstName=firstName lastName=lastName}}',\n    '{{#my-component firstName=firstName  lastName=lastName as |fullName|}} {{fullName}}{{/my-component}}',\n    '<a class=\"btn\">{{btnLabel}}</a>',\n    '{{employee-profile employee=(hash firstName=firstName lastName=lastName age=age)}}',\n    '{{employee-profile employee=(hash fullName=(hash firstName=firstName lastName=lastName) age=age)}}',\n  ],\n  invalid: [\n    {\n      code: '{{my-component firstName=firstName lastName=lastName firstName=firstName}}',\n      output: '{{my-component firstName=firstName lastName=lastName}}',\n      errors: [{ messageId: 'duplicateMustache', data: { name: 'firstName' } }],\n    },\n    {\n      code: '{{#my-component firstName=firstName  lastName=lastName firstName=firstName as |fullName|}} {{fullName}}{{/my-component}}',\n      output:\n        '{{#my-component firstName=firstName  lastName=lastName as |fullName|}} {{fullName}}{{/my-component}}',\n      errors: [{ messageId: 'duplicateBlock', data: { name: 'firstName' } }],\n    },\n    {\n      code: '<a class=\"btn\" class=\"btn\">{{btnLabel}}</a>',\n      output: '<a class=\"btn\">{{btnLabel}}</a>',\n      errors: [{ messageId: 'duplicateElement', data: { name: 'class' } }],\n    },\n    {\n      code: '{{employee-profile employee=(hash firstName=firstName lastName=lastName age=age firstName=firstName)}}',\n      output: '{{employee-profile employee=(hash firstName=firstName lastName=lastName age=age)}}',\n      errors: [{ messageId: 'duplicateSubExpr', data: { name: 'firstName' } }],\n    },\n    {\n      code: '{{employee-profile employee=(hash fullName=(hash firstName=firstName lastName=lastName firstName=firstName) age=age)}}',\n      output:\n        '{{employee-profile employee=(hash fullName=(hash firstName=firstName lastName=lastName) age=age)}}',\n      errors: [{ messageId: 'duplicateSubExpr', data: { name: 'firstName' } }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-duplicate-id.js",
    "content": "const rule = require('../../../lib/rules/template-no-duplicate-id');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-duplicate-id', rule, {\n  valid: [\n    '<template><div id=\"a\"></div><div id=\"b\"></div></template>',\n    '<template><div id=\"id-00\"></div><div id=\"id-01\"></div></template>',\n    '<template><div id={{unique-id}}></div><div id={{unique-id}}></div></template>',\n    '<template><div id=\"{{unique-id}}\"></div><div id=\"{{unique-id}}\"></div></template>',\n    \"<template><div id='{{unique-id}}'></div><div id='{{unique-id}}'></div></template>\",\n    '<template><div id=\"{{(unique-id)}}\"></div><div id=\"{{(unique-id)}}\"></div></template>',\n    '<template><div id={{(unique-id)}}></div><div id={{(unique-id)}}></div></template>',\n    '<template><div id={{\"id-00\"}}></div></template>',\n    '<template><div id={{this.divId00}}></div></template>',\n    '<template><div id={{this.divId00}}></div><div id={{this.divId01}}></div></template>',\n    '<template><div id=\"concat-{{this.divId}}\"></div></template>',\n    '<template><div id=\"concat-{{this.divId00}}\"></div><div id=\"concat-{{this.divId01}}\"></div></template>',\n    '<template><div id={{id-00}}></div><div id=\"id-00\"></div></template>',\n    '<template><div id=\"id-00\"></div><div id={{id-00}}></div></template>',\n    '<template><div id=\"concat-{{id-00}}\"></div><div id=\"concat-id-00\"></div></template>',\n    '<template><div id=\"concat-id-00\"></div><div id=\"concat-{{id-00}}\"></div></template>',\n    '<template><div id=\"id-00\"></div>{{#foo elementId=\"id-01\"}}{{/foo}}</template>',\n    '<template>{{#foo elementId=\"id-01\"}}{{/foo}}<div id=\"id-00\"></div></template>',\n    '<template>{{#if}}<div id=\"id-00\"></div>{{else}}<span id=\"id-00\"></span>{{/if}}</template>',\n    '<template><div id={{1234}}></div></template>',\n    '<template><div id={{1234}}></div><div id={{\"1234\"}}></div></template>',\n    '<template><div id={{\"id-00\"}}></div><div id={{\"id-01\"}}></div></template>',\n    '<template><div id={{this.foo}}></div><div id={{this.bar}}></div></template>',\n    '<template>{{foo id=\"id-00\"}}{{foo id=\"id-01\"}}</template>',\n    '<template><div id=\"partA{{partB}}{{\"partC\"}}\"></div><div id=\"{{\"partA\"}}{{\"partB\"}}partC\"></div></template>',\n    `<template>\n      {{#if this.foo}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/if}}\n    </template>`,\n    `<template>\n      {{#if this.foo}}\n        <div id=\"id-00\"></div>\n      {{else if this.bar}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/if}}\n    </template>`,\n    `<template>\n      {{#unless this.foo}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/unless}}\n    </template>`,\n    `<template>\n      {{#unless this.foo}}\n        <div id=\"id-00\"></div>\n      {{else unless this.bar}}\n        <div id=\"id-00\"></div>\n      {{else if this.baz}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/unless}}\n    </template>`,\n    `<template>\n      {{#let blah.id as |footerId|}}\n        {{#if this.foo}}\n          <div id={{footerId}}></div>\n        {{else}}\n          <span id={{footerId}}></span>\n        {{/if}}\n      {{/let}}\n    </template>`,\n    `<template>\n      {{#let 'foobar' as |footerId|}}\n        {{#if this.foo}}\n          <div id={{footerId}}></div>\n        {{else}}\n          <span id={{footerId}}></span>\n        {{/if}}\n      {{/let}}\n    </template>`,\n    `<template>\n      {{#if this.foo}}\n        <div id={{this.divId00}}></div>\n      {{else}}\n        <div id={{this.divId00}}></div>\n      {{/if}}\n    </template>`,\n    `<template>\n      {{#if this.foo}}\n        {{#if this.other}}\n          <div id=\"nested\"></div>\n        {{else}}\n          <div id=\"nested\"></div>\n        {{/if}}\n        <div id=\"root\"></div>\n      {{else}}\n        <div id=\"nested\"></div>\n      {{/if}}\n    </template>`,\n    `<template>\n      <MyComponent as |inputProperties|>\n        <Input id={{inputProperties.id}} />\n        <div id={{inputProperties.abc}} />\n      </MyComponent>\n\n      <MyComponent as |inputProperties|>\n        <Input id={{inputProperties.id}} />\n      </MyComponent>\n    </template>`,\n    // IDs in different branches of {{#each}} should not conflict\n    `<template>\n      {{#each items as |item|}}\n        <div id=\"x\"></div>\n      {{else}}\n        <div id=\"x\"></div>\n      {{/each}}\n    </template>`,\n  ],\n  invalid: [\n    // blockParams on an element must not isolate static IDs from outer scope\n    {\n      code: `<template>\n      <MyComponent as |foo|>\n        <div id=\"shared-id\"></div>\n      </MyComponent>\n      <div id=\"shared-id\"></div>\n    </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n\n    {\n      code: '<template><div id=\"id-00\"></div><div id=\"id-00\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div><div id=\"id-01\"></div></div><div><div id=\"id-01\"></div></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id=\"id-00\"></div><div id={{\"id-00\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id={{\"id-00\"}}></div><div id=\"id-00\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id=\"id-00\"></div><div id=\"id-{{\"00\"}}\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id=\"id-00\"></div><div id=\"{{\"id\"}}-00\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id=\"id-00\"></div>{{#foo elementId=\"id-00\"}}{{/foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo elementId=\"id-00\"}}{{/foo}}<div id=\"id-00\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id={{\"id-00\"}}></div>{{#foo elementId=\"id-00\"}}{{/foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo elementId=\"id-00\"}}{{/foo}}<div id={{\"id-00\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id=\"id-{{\"00\"}}\"></div>{{#foo elementId=\"id-00\"}}{{/foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo elementId=\"id-00\"}}{{/foo}}<div id=\"id-{{\"00\"}}\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo elementId=\"id-00\"}}{{/foo}}{{#bar elementId=\"id-00\"}}{{/bar}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{foo id=\"id-00\"}}{{foo id=\"id-00\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id={{1234}}></div><div id={{1234}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id={{this.divId00}}></div><div id={{this.divId00}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><div id=\"partA{{partB}}{{\"partC\"}}\"></div><div id=\"{{\"partA\"}}{{partB}}partC\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo elementId=\"id-00\"}}{{/foo}}{{bar elementId=\"id-00\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo id=\"id-00\"}}{{/foo}}{{bar id=\"id-00\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo id=\"id-00\"}}{{/foo}}<Bar id=\"id-00\" /></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo id=\"id-00\"}}{{/foo}}<Bar @id=\"id-00\" /></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template>{{#foo id=\"id-00\"}}{{/foo}}<Bar @elementId=\"id-00\" /></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n      {{#if this.foo}}\n        <div id={{this.divId00}}></div>\n        <div id={{this.divId00}}></div>\n      {{else}}\n        <div id=\"other-thing\"></div>\n      {{/if}}\n    </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n        <div id=\"id-00\"></div>\n        {{#if this.foo}}\n          <div id=\"id-00\"></div>\n        {{/if}}\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n      <div id={{this.divId00}}></div>\n      {{#if this.foo}}\n        <div id={{this.divId00}}></div>\n      {{else}}\n        <div id={{this.divId00}}></div>\n      {{/if}}\n    </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }, { messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n        {{#if this.foo}}\n          <div id=\"otherid\"></div>\n        {{else}}\n          <div id=\"anidhere\"></div>\n        {{/if}}\n        <div id=\"anidhere\"></div>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n        {{#if this.foo}}\n          {{#if this.other}}\n            <div id=\"nested\"></div>\n          {{/if}}\n        {{else}}\n          <div id=\"nested\"></div>\n        {{/if}}\n        <div id=\"nested\"></div>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n        {{#if this.foo}}\n          {{#if this.other}}\n            <div id={{(hello-world)}}></div>\n          {{/if}}\n        {{else}}\n          <div id={{(hello-world)}}></div>\n        {{/if}}\n        <div id={{(hello-world)}}></div>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<template>\n        <MyComponent as |inputProperties|>\n          <Input id={{inputProperties.id}} />\n          <Input id={{inputProperties.id}} />\n        </MyComponent>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-duplicate-id (hbs)', rule, {\n  valid: [\n    '<div id=\"id-00\"></div><div id=\"id-01\"></div>',\n    '<div id={{unique-id}}></div><div id={{unique-id}}></div>',\n    '<div id=\"{{unique-id}}\"></div><div id=\"{{unique-id}}\"></div>',\n    \"<div id='{{unique-id}}'></div><div id='{{unique-id}}'></div>\",\n    '<div id=\"{{(unique-id)}}\"></div><div id=\"{{(unique-id)}}\"></div>',\n    '<div id={{(unique-id)}}></div><div id={{(unique-id)}}></div>',\n    '<div id={{\"id-00\"}}></div>',\n    '<div id={{this.divId00}}></div>',\n    '<div id={{this.divId00}}></div><div id={{this.divId01}}></div>',\n    '<div id=\"concat-{{this.divId}}\"></div>',\n    '<div id=\"concat-{{this.divId00}}\"></div><div id=\"concat-{{this.divId01}}\"></div>',\n    '<div id={{id-00}}></div><div id=\"id-00\"></div>',\n    '<div id=\"id-00\"></div><div id={{id-00}}></div>',\n    '<div id=\"concat-{{id-00}}\"></div><div id=\"concat-id-00\"></div>',\n    '<div id=\"concat-id-00\"></div><div id=\"concat-{{id-00}}\"></div>',\n    '<div id=\"id-00\"></div>{{#foo elementId=\"id-01\"}}{{/foo}}',\n    '{{#foo elementId=\"id-01\"}}{{/foo}}<div id=\"id-00\"></div>',\n    '{{#if}}<div id=\"id-00\"></div>{{else}}<span id=\"id-00\"></span>{{/if}}',\n    '<div id={{1234}}></div>',\n    '<div id={{1234}}></div><div id={{\"1234\"}}></div>',\n    '<div id={{\"id-00\"}}></div><div id={{\"id-01\"}}></div>',\n    '<div id={{this.foo}}></div><div id={{this.bar}}></div>',\n    '{{foo id=\"id-00\"}}{{foo id=\"id-01\"}}',\n    '<div id=\"partA{{partB}}{{\"partC\"}}\"></div><div id=\"{{\"partA\"}}{{\"partB\"}}partC\"></div>',\n    `{{#if this.foo}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/if}}`,\n    `{{#if this.foo}}\n        <div id=\"id-00\"></div>\n      {{else if this.bar}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/if}}`,\n    `{{#unless this.foo}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/unless}}`,\n    `{{#unless this.foo}}\n        <div id=\"id-00\"></div>\n      {{else unless this.bar}}\n        <div id=\"id-00\"></div>\n      {{else if this.baz}}\n        <div id=\"id-00\"></div>\n      {{else}}\n        <div id=\"id-00\"></div>\n      {{/unless}}`,\n    `{{#let blah.id as |footerId|}}\n        {{#if this.foo}}\n          <div id={{footerId}}></div>\n        {{else}}\n          <span id={{footerId}}></span>\n        {{/if}}\n      {{/let}}`,\n    `{{#let 'foobar' as |footerId|}}\n        {{#if this.foo}}\n          <div id={{footerId}}></div>\n        {{else}}\n          <span id={{footerId}}></span>\n        {{/if}}\n      {{/let}}`,\n    `{{#if this.foo}}\n        <div id={{this.divId00}}></div>\n      {{else}}\n        <div id={{this.divId00}}></div>\n      {{/if}}`,\n    `{{#if this.foo}}\n        <div id=\"partA{{partB}}{{\"partC\"}}\"></div>\n      {{else}}\n        <div id=\"partA{{partB}}{{\"partC\"}}\"></div>\n      {{/if}}`,\n    `{{#if this.foo}}\n        {{#if this.other}}\n          <div id=\"nested\"></div>\n        {{else}}\n          <div id=\"nested\"></div>\n        {{/if}}\n        <div id=\"root\"></div>\n      {{else}}\n        <div id=\"nested\"></div>\n      {{/if}}`,\n    `<MyComponent as |inputProperties|>\n        <Input id={{inputProperties.id}} />\n        <div id={{inputProperties.abc}} />\n      </MyComponent>\n\n      <MyComponent as |inputProperties|>\n        <Input id={{inputProperties.id}} />\n      </MyComponent>`,\n    // IDs in different branches of {{#each}} should not conflict\n    `{{#each items as |item|}}\n        <div id=\"x\"></div>\n      {{else}}\n        <div id=\"x\"></div>\n      {{/each}}`,\n  ],\n  invalid: [\n    // blockParams on an element must not isolate static IDs from outer scope\n    {\n      code: `<MyComponent as |foo|>\n        <div id=\"shared-id\"></div>\n      </MyComponent>\n      <div id=\"shared-id\"></div>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"id-00\"></div><div id=\"id-00\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div><div id=\"id-01\"></div></div><div><div id=\"id-01\"></div></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"id-00\"></div><div id={{\"id-00\"}}></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id={{\"id-00\"}}></div><div id=\"id-00\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"id-00\"></div><div id=\"id-{{\"00\"}}\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"id-00\"></div><div id=\"{{\"id\"}}-00\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"id-00\"></div>{{#foo elementId=\"id-00\"}}{{/foo}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo elementId=\"id-00\"}}{{/foo}}<div id=\"id-00\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id={{\"id-00\"}}></div>{{#foo elementId=\"id-00\"}}{{/foo}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo elementId=\"id-00\"}}{{/foo}}<div id={{\"id-00\"}}></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"id-{{\"00\"}}\"></div>{{#foo elementId=\"id-00\"}}{{/foo}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo elementId=\"id-00\"}}{{/foo}}<div id=\"id-{{\"00\"}}\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo elementId=\"id-00\"}}{{/foo}}{{#bar elementId=\"id-00\"}}{{/bar}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{foo id=\"id-00\"}}{{foo id=\"id-00\"}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id={{1234}}></div><div id={{1234}}></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id={{this.divId00}}></div><div id={{this.divId00}}></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<div id=\"partA{{partB}}{{\"partC\"}}\"></div><div id=\"{{\"partA\"}}{{partB}}partC\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo elementId=\"id-00\"}}{{/foo}}{{bar elementId=\"id-00\"}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo id=\"id-00\"}}{{/foo}}{{bar id=\"id-00\"}}',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo id=\"id-00\"}}{{/foo}}<Bar id=\"id-00\" />',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo id=\"id-00\"}}{{/foo}}<Bar @id=\"id-00\" />',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '{{#foo id=\"id-00\"}}{{/foo}}<Bar @elementId=\"id-00\" />',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `{{#if this.foo}}\n        <div id={{this.divId00}}></div>\n        <div id={{this.divId00}}></div>\n      {{else}}\n        <div id=\"other-thing\"></div>\n      {{/if}}`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<div id=\"id-00\"></div>\n        {{#if this.foo}}\n          <div id=\"id-00\"></div>\n        {{/if}}`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<div id={{this.divId00}}></div>\n      {{#if this.foo}}\n        <div id={{this.divId00}}></div>\n      {{else}}\n        <div id={{this.divId00}}></div>\n      {{/if}}`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }, { messageId: 'duplicate' }],\n    },\n    {\n      code: `{{#if this.foo}}\n          <div id=\"otherid\"></div>\n        {{else}}\n          <div id=\"anidhere\"></div>\n        {{/if}}\n        <div id=\"anidhere\"></div>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `{{#if this.foo}}\n          {{#if this.other}}\n            <div id=\"nested\"></div>\n          {{/if}}\n        {{else}}\n          <div id=\"nested\"></div>\n        {{/if}}\n        <div id=\"nested\"></div>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `{{#if this.foo}}\n          {{#if this.other}}\n            <div id={{(hello-world)}}></div>\n          {{/if}}\n        {{else}}\n          <div id={{(hello-world)}}></div>\n        {{/if}}\n        <div id={{(hello-world)}}></div>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: `<MyComponent as |inputProperties|>\n          <Input id={{inputProperties.id}} />\n          <Input id={{inputProperties.id}} />\n        </MyComponent>`,\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-duplicate-landmark-elements.js",
    "content": "const rule = require('../../../lib/rules/template-no-duplicate-landmark-elements');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-duplicate-landmark-elements', rule, {\n  valid: [\n    '<template><nav aria-label=\"primary site navigation\"></nav><nav aria-label=\"secondary site navigation within home page\"></nav></template>',\n    '<template><nav aria-label=\"primary site navigation\"></nav><div role=\"navigation\" aria-label=\"secondary site navigation within home page\"></div></template>',\n    '<template><form aria-labelledby=\"form-title\"><div id=\"form-title\">Shipping Address</div></form><form aria-label=\"meaningful title of second form\"></form></template>',\n    '<template><form role=\"search\"></form><form></form></template>',\n    '<template><header></header><main></main><footer></footer></template>',\n    '<template><img role=\"none\"><img role=\"none\"></template>',\n    // Conditional branches: elements in if/else are mutually exclusive\n    '<template>{{#if this.isCreateProjectFromSavedSearchEnabled}}<form></form>{{else}}<form></form>{{/if}}</template>',\n    // header inside sectioning element loses landmark role\n    \"<template><main><header><h1>Main Page Header</h1></header></main><dialog id='my-dialog'><header><h1>Dialog Header</h1></header></dialog></template>\",\n    // Landmarks inside dialog are in a separate scope\n    '<template><nav></nav><dialog><nav></nav></dialog></template>',\n    // Landmarks inside popover element are in a separate scope\n    '<template><nav></nav><div popover><nav></nav></div></template>',\n    // Dynamic role values — can't determine role statically\n    '<template><div role={{this.role}}></div><div role={{this.role}}></div></template>',\n    // Dynamic aria-label on one landmark — can't infer whether it duplicates a sibling\n    '<template><form></form><form aria-label={{this.formLabel}}></form></template>',\n    '<template><nav></nav><nav aria-label={{this.navLabel}}></nav></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><nav></nav><nav></nav></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><nav></nav><div role=\"navigation\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><nav></nav><nav aria-label=\"secondary navigation\"></nav></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><main></main><div role=\"main\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><nav aria-label=\"site navigation\"></nav><nav aria-label=\"site navigation\"></nav></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><form aria-label=\"search-form\"></form><form aria-label=\"search-form\"></form></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<template><form aria-labelledby=\"form-title\"></form><form aria-labelledby=\"form-title\"></form></template>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-duplicate-landmark-elements (hbs)', rule, {\n  valid: [\n    '<nav aria-label=\"primary site navigation\"></nav><nav aria-label=\"secondary site navigation within home page\"></nav>',\n    '<nav aria-label=\"primary site navigation\"></nav><div role=\"navigation\" aria-label=\"secondary site navigation within home page\"></div>',\n    '<nav aria-label=\"primary site navigation\"></nav><div role={{role}} aria-label=\"secondary site navigation within home page\"></div>',\n    '<form aria-labelledby=\"form-title\"><div id=\"form-title\">Shipping Address</div></form><form aria-label=\"meaningful title of second form\"></form>',\n    '<form role=\"search\"></form><form></form>',\n    '<header></header><main></main><footer></footer>',\n    '<img role=\"none\"><img role=\"none\">',\n    // Dynamic aria-label values are treated as unique (can't statically determine duplicates)\n    '<nav aria-label={{siteNavigation}}></nav><nav aria-label={{siteNavigation}}></nav>',\n    '<nav aria-label=\"primary navigation\"></nav><nav aria-label={{this.something}}></nav>',\n    // Dynamic aria-label on one landmark sibling of an unlabeled landmark — can't infer duplication\n    '<form></form><form aria-label={{this.formLabel}}></form>',\n    '<nav></nav><nav aria-label={{this.navLabel}}></nav>',\n    // header/footer inside sectioning elements lose their landmark role\n    \"<main><header><h1>Main Page Header</h1></header><button commandfor='my-dialog'>Open Dialog</button></main><dialog id='my-dialog'><header><h1>Dialog Header</h1></header></dialog>\",\n    \"<main><header><h1>Main Page Header</h1></header><button commandfor='my-dialog'>Open Dialog</button></main><div popover id='my-dialog'><header><h1>Dialog Header</h1></header></div>\",\n    // Conditional branches: elements in if/else are mutually exclusive\n    '{{#if this.isCreateProjectFromSavedSearchEnabled}}<form></form>{{else}}<form></form>{{/if}}',\n    // Landmarks inside dialog are in a separate scope\n    '<nav></nav><dialog><nav></nav></dialog>',\n    // Landmarks inside popover element are in a separate scope\n    '<nav></nav><div popover><nav></nav></div>',\n    // Dynamic role values — can't determine role statically\n    '<div role={{this.role}}></div><div role={{this.role}}></div>',\n    // Each blocks: landmarks in each body are scoped\n    '{{#each this.items as |item|}}<nav aria-label={{item.label}}></nav>{{/each}}',\n  ],\n  invalid: [\n    {\n      code: '<nav></nav><nav></nav>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<nav></nav><div role=\"navigation\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<nav></nav><nav aria-label=\"secondary navigation\"></nav>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<main></main><div role=\"main\"></div>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<nav aria-label=\"site navigation\"></nav><nav aria-label=\"site navigation\"></nav>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<form aria-label=\"search-form\"></form><form aria-label=\"search-form\"></form>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n    {\n      code: '<form aria-labelledby=\"form-title\"></form><form aria-labelledby=\"form-title\"></form>',\n      output: null,\n      errors: [{ messageId: 'duplicate' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-dynamic-subexpression-invocations.js",
    "content": "const rule = require('../../../lib/rules/template-no-dynamic-subexpression-invocations');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-dynamic-subexpression-invocations', rule, {\n  valid: [\n    '<template>{{format-date this.date}}</template>',\n    '<template>{{(upper-case this.name)}}</template>',\n    '<template>{{helper \"static\"}}</template>',\n\n    '<template>{{something \"here\"}}</template>',\n    '<template>{{something}}</template>',\n    '<template>{{something here=\"goes\"}}</template>',\n    '<template><button onclick={{fn something \"here\"}}></button></template>',\n    '<template>{{@thing \"somearg\"}}</template>',\n    '<template><Foo @bar=\"asdf\" /></template>',\n    '<template><Foo @bar={{\"asdf\"}} /></template>',\n    '<template><Foo @bar={{true}} /></template>',\n    '<template><Foo @bar={{false}} /></template>',\n    '<template><Foo @bar={{undefined}} /></template>',\n    '<template><Foo @bar={{null}} /></template>',\n    '<template><Foo @bar={{1}} /></template>',\n    '<template>{{1}}</template>',\n    '<template>{{true}}</template>',\n    '<template>{{null}}</template>',\n    '<template>{{undefined}}</template>',\n    '<template>{{\"foo\"}}</template>',\n    // MustacheStatements in body context are not flagged (only attr context is)\n    '<template>{{this.formatter this.data}}</template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template>{{(this.helper \"arg\")}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not use dynamic helper invocations. Use explicit helper names instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n    {\n      code: '<template>{{(@helperName \"value\")}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not use dynamic helper invocations. Use explicit helper names instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n\n    {\n      code: '<template><Foo bar=\"{{@thing \"some-arg\"}}\" /></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><Foo {{this.foo}} /></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><Foo {{@foo}} /></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><Foo {{foo.bar}} /></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><button onclick={{@thing \"some-arg\"}}></button></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template>{{#let \"whatever\" as |thing|}}<button onclick={{thing \"some-arg\"}}></button>{{/let}}</template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><button onclick={{this.thing \"some-arg\"}}></button></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><button onclick={{lol.other.path \"some-arg\"}}></button></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template>{{if (this.foo) \"true\" \"false\"}}</template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><Foo @bar={{@thing \"some-arg\"}} /></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<template><Foo onclick={{@thing \"some-arg\"}} /></template>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-dynamic-subexpression-invocations', rule, {\n  valid: [\n    '{{something \"here\"}}',\n    '{{something}}',\n    '{{something here=\"goes\"}}',\n    '<button onclick={{fn something \"here\"}}></button>',\n    '{{@thing \"somearg\"}}',\n    '<Foo @bar=\"asdf\" />',\n    '<Foo @bar={{\"asdf\"}} />',\n    '<Foo @bar={{true}} />',\n    '<Foo @bar={{false}} />',\n    '<Foo @bar={{undefined}} />',\n    '<Foo @bar={{null}} />',\n    '<Foo @bar={{1}} />',\n    '{{1}}',\n    '{{true}}',\n    '{{null}}',\n    '{{undefined}}',\n    '{{\"foo\"}}',\n  ],\n  invalid: [\n    {\n      code: '<Foo bar=\"{{@thing \"some-arg\"}}\" />',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<Foo {{this.foo}} />',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<Foo {{@foo}} />',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<Foo {{foo.bar}} />',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<button onclick={{@thing \"some-arg\"}}></button>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '{{#let \"whatever\" as |thing|}}<button onclick={{thing \"some-arg\"}}></button>{{/let}}',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<button onclick={{this.thing \"some-arg\"}}></button>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<button onclick={{lol.other.path \"some-arg\"}}></button>',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '{{if (this.foo) \"true\" \"false\"}}',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<Foo @bar={{@thing \"some-arg\"}} />',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n    {\n      code: '<Foo onclick={{@thing \"some-arg\"}} />',\n      output: null,\n      errors: [\n        { message: 'Do not use dynamic helper invocations. Use explicit helper names instead.' },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-element-event-actions.js",
    "content": "const { RuleTester } = require('eslint');\nconst rule = require('../../../lib/rules/template-no-element-event-actions');\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-element-event-actions', rule, {\n  valid: [\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <button {{on \"click\" this.handleClick}}>Click</button>\n          </template>\n        }\n      `,\n      output: null,\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div>No events</div>\n          </template>\n        }\n      `,\n      output: null,\n    },\n    // requireActionHelper: true — non-action mustache is not flagged\n    {\n      filename: 'my-component.gjs',\n      code: '<template><button type=\"button\" onclick={{this.myAction}}></button></template>',\n      output: null,\n      options: [{ requireActionHelper: true }],\n    },\n    // requireActionHelper: false — string event handler is not flagged\n    {\n      filename: 'my-component.gjs',\n      code: '<template><button type=\"button\" onclick=\"myFunction()\"></button></template>',\n      output: null,\n      options: [{ requireActionHelper: false }],\n    },\n  ],\n\n  invalid: [\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <button onclick={{this.handleClick}}>Click</button>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'noElementEventActions',\n        },\n      ],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <div onmouseenter={{this.handleHover}}>Hover</div>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'noElementEventActions',\n        },\n      ],\n    },\n    // requireActionHelper: false — any mustache on event attribute is flagged\n    {\n      filename: 'my-component.gjs',\n      code: '<template><button type=\"button\" onclick={{this.myAction}}></button></template>',\n      output: null,\n      options: [{ requireActionHelper: false }],\n      errors: [{ messageId: 'noElementEventActions' }],\n    },\n    // requireActionHelper: true — only {{action ...}} mustaches are flagged\n    {\n      filename: 'my-component.gjs',\n      code: '<template><button onclick={{action \"myAction\"}}></button></template>',\n      output: null,\n      options: [{ requireActionHelper: true }],\n      errors: [{ messageId: 'noElementEventActions' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-element-event-actions (hbs)', rule, {\n  valid: [\n    '<button></button>',\n    '<button type=\"button\" onclick=\"myFunction()\"></button>',\n    '<button type=\"button\" {{on \"click\" this.handleClick}}></button>',\n    {\n      code: '<button type=\"button\" onclick={{this.myAction}}></button>',\n      options: [{ requireActionHelper: true }],\n    },\n    {\n      code: '<button type=\"button\" onclick=\"myFunction()\"></button>',\n      options: [{ requireActionHelper: false }],\n    },\n  ],\n  invalid: [\n    {\n      code: '<button onclick={{action \"myAction\"}}></button>',\n      output: null,\n      errors: [{ messageId: 'noElementEventActions' }],\n    },\n    {\n      code: '<button type=\"button\" onclick={{this.myAction}}></button>',\n      output: null,\n      errors: [{ messageId: 'noElementEventActions' }],\n    },\n    {\n      code: '<button type=\"button\" onclick={{this.myAction}}></button>',\n      output: null,\n      options: [{ requireActionHelper: false }],\n      errors: [{ messageId: 'noElementEventActions' }],\n    },\n    {\n      code: '<button onclick={{action \"myAction\"}}></button>',\n      output: null,\n      options: [{ requireActionHelper: true }],\n      errors: [{ messageId: 'noElementEventActions' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-empty-headings.js",
    "content": "const rule = require('../../../lib/rules/template-no-empty-headings');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-empty-headings', rule, {\n  valid: [\n    '<template><h1>Title</h1></template>',\n    '<template><h2>{{this.title}}</h2></template>',\n    '<template><h3><span>Text</span></h3></template>',\n    '<template><h4 hidden></h4></template>',\n    '<template><h1>Accessible Heading</h1></template>',\n    '<template><h1>Accessible&nbsp;Heading</h1></template>',\n    '<template><h1 aria-hidden=\"true\">Valid Heading</h1></template>',\n    '<template><h1 aria-hidden=\"true\"><span>Valid Heading</span></h1></template>',\n    '<template><h1 aria-hidden=\"false\">Accessible Heading</h1></template>',\n    '<template><h1 hidden>Valid Heading</h1></template>',\n    '<template><h1 hidden><span>Valid Heading</span></h1></template>',\n    '<template><h1><span aria-hidden=\"true\">Hidden text</span><span>Visible text</span></h1></template>',\n    '<template><h1><span aria-hidden=\"true\">Hidden text</span>Visible text</h1></template>',\n    '<template><div role=\"heading\" aria-level=\"1\">Accessible Text</div></template>',\n    '<template><div role=\"heading\" aria-level=\"1\"><span>Accessible Text</span></div></template>',\n    '<template><div role=\"heading\" aria-level=\"1\"><span aria-hidden=\"true\">Hidden text</span><span>Visible text</span></div></template>',\n    '<template><div role=\"heading\" aria-level=\"1\"><span aria-hidden=\"true\">Hidden text</span>Visible text</div></template>',\n    '<template><div></div></template>',\n    '<template><p></p></template>',\n    '<template><span></span></template>',\n    '<template><header></header></template>',\n    '<template><h2><CustomComponent /></h2></template>',\n    '<template><h2>{{@title}}</h2></template>',\n    '<template><h2>{{#component}}{{/component}}</h2></template>',\n    '<template><h2><span>{{@title}}</span></h2></template>',\n    '<template><h2><div><CustomComponent /></div></h2></template>',\n    '<template><h2><div></div><CustomComponent /></h2></template>',\n    '<template><h2><div><span>{{@title}}</span></div></h2></template>',\n    '<template><h2><span>Some text{{@title}}</span></h2></template>',\n    '<template><h2><span><div></div>{{@title}}</span></h2></template>',\n\n    // Non-PascalCase component forms count as accessible content\n    '<template><h1><this.Heading /></h1></template>',\n    '<template><h2><@heading /></h2></template>',\n    '<template><h3><ns.Heading /></h3></template>',\n\n    // Explicit \"true\" exempts the empty-heading check — author has\n    // signalled the heading is intentionally hidden from assistive tech.\n    '<template><h1 aria-hidden={{true}}></h1></template>',\n    '<template><h1 aria-hidden=\"true\">Visible to sighted only</h1></template>',\n    '<template><h1 aria-hidden=\"TRUE\"></h1></template>',\n    '<template><h1 aria-hidden=\"True\"></h1></template>',\n    '<template><h1 aria-hidden={{\"TRUE\"}}></h1></template>',\n    '<template><h1 aria-hidden={{\"True\"}}></h1></template>',\n    // Quoted-mustache (GlimmerConcatStatement) forms — `aria-hidden=\"{{true}}\"`\n    // resolves the same as `aria-hidden={{true}}`. Pin these so future\n    // refactors don't regress concat handling.\n    '<template><h1 aria-hidden=\"{{true}}\"></h1></template>',\n    '<template><h1 aria-hidden=\"{{\"true\"}}\"></h1></template>',\n    // Whitespace normalization — incidental surrounding whitespace should\n    // still resolve to \"true\".\n    '<template><h1 aria-hidden={{\" true \"}}></h1></template>',\n    '<template><h1 aria-hidden=\" true \"></h1></template>',\n\n    // Custom elements (hyphenated lowercase) aren't in the html-tags / svg-tags /\n    // mathml-tag-names allowlists — treated as opaque, assume content. Matches\n    // the accepted-false-negative convention established in #2689.\n    '<template><h1><my-widget /></h1></template>',\n    '<template><h2><x-foo>text</x-foo></h2></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><h1></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h2>   </h2></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: `<template><h1>\n &nbsp;</h1></template>`,\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span></span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: `<template><h1><span>\n &nbsp;</span></h1></template>`,\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><div><span></span></div></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span></span><span></span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1> &nbsp; <div aria-hidden=\"true\">Some hidden text</div></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span aria-hidden=\"true\">Inaccessible text</span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span hidden>Inaccessible text</span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span hidden>{{@title}}</span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span hidden>{{#component}}Inaccessible text{{/component}}</span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span hidden><CustomComponent>Inaccessible text</CustomComponent></span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1><span aria-hidden=\"true\">Hidden text</span><span aria-hidden=\"true\">Hidden text</span></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><div role=\"heading\" aria-level=\"1\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><div role=\"heading\" aria-level=\"1\"><span aria-hidden=\"true\">Inaccessible text</span></div></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><div role=\"heading\" aria-level=\"1\"><span hidden>Inaccessible text</span></div></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n\n    // Explicit falsy aria-hidden does NOT exempt the empty-heading check —\n    // this is the unambiguous opt-out, no ecosystem position disagrees.\n    {\n      code: '<template><h1 aria-hidden=\"false\"></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1 aria-hidden={{false}}></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1 aria-hidden={{\"false\"}}></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    // Per the WAI-ARIA 1.2 `aria-hidden` value table\n    // (https://www.w3.org/TR/wai-aria-1.2/#aria-hidden): valueless /\n    // empty-string `aria-hidden` resolves to the default `undefined`,\n    // not `true`. Empty headings with these forms still flag.\n    {\n      code: '<template><h1 aria-hidden></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1 aria-hidden=\"\"></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    // Mustache / concat forms that resolve to an empty / whitespace-only\n    // string — same spec-aligned treatment.\n    {\n      code: '<template><h1 aria-hidden={{\"\"}}></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1 aria-hidden=\"{{\"\"}}\"></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n    {\n      code: '<template><h1 aria-hidden={{\" \"}}></h1></template>',\n      output: null,\n      errors: [{ messageId: 'emptyHeading' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-extra-mut-helper-argument.js",
    "content": "const rule = require('../../../lib/rules/template-no-extra-mut-helper-argument');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-extra-mut-helper-argument', rule, {\n  valid: [\n    '<template>{{my-component click=(action (mut isClicked))}}</template>',\n    '<template>{{my-component click=(action (mut isClicked) true)}}</template>',\n    '<template>{{my-component isClickedMutable=(mut isClicked)}}</template>',\n    '<template><button {{action (mut isClicked)}}></button></template>',\n    '<template><button {{action (mut isClicked) true}}></button></template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{my-component click=(action (mut isClicked true))}}</template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.',\n        },\n      ],\n    },\n    {\n      code: '<template>{{my-component isClickedMutable=(mut isClicked true)}}</template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.',\n        },\n      ],\n    },\n    {\n      code: '<template><button {{action (mut isClicked true)}}></button></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-extra-mut-helper-argument', rule, {\n  valid: [\n    '{{my-component click=(action (mut isClicked))}}',\n    '{{my-component click=(action (mut isClicked) true)}}',\n    '{{my-component isClickedMutable=(mut isClicked)}}',\n    '<button {{action (mut isClicked)}}></button>',\n    '<button {{action (mut isClicked) true}}></button>',\n  ],\n  invalid: [\n    {\n      code: '{{my-component click=(action (mut isClicked true))}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.',\n        },\n      ],\n    },\n    {\n      code: '{{my-component isClickedMutable=(mut isClicked true)}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.',\n        },\n      ],\n    },\n    {\n      code: '<button {{action (mut isClicked true)}}></button>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The handlebars `mut(attr)` helper should only have one argument passed to it. To pass a value, use: `(action (mut attr) value)`.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-forbidden-elements.js",
    "content": "const rule = require('../../../lib/rules/template-no-forbidden-elements');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-forbidden-elements', rule, {\n  valid: [\n    { code: '<template><div></div></template>', options: [['script']] },\n    // Object config form\n    { code: '<template><div></div></template>', options: [{ forbidden: ['script'] }] },\n    { code: '<template><script></script></template>', options: [{ forbidden: ['html'] }] },\n    '<template><header></header></template>',\n    '<template><footer></footer></template>',\n    '<template><p></p></template>',\n    '<template><head><meta charset=\"utf-8\"></head></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><script></script></template>',\n      output: null,\n      options: [{ forbidden: ['script'] }],\n      errors: [{ messageId: 'forbidden' }],\n    },\n    {\n      code: '<template><script></script></template>',\n      output: null,\n      options: [['script']],\n      errors: [{ messageId: 'forbidden' }],\n    },\n\n    {\n      code: '<template><html></html></template>',\n      output: null,\n      errors: [{ messageId: 'forbidden' }],\n    },\n    {\n      code: '<template><style></style></template>',\n      output: null,\n      errors: [{ messageId: 'forbidden' }],\n    },\n    {\n      code: '<template><meta charset=\"utf-8\"></template>',\n      output: null,\n      errors: [{ messageId: 'forbidden' }],\n    },\n    {\n      code: '<template><head><html></html></head></template>',\n      output: null,\n      errors: [{ messageId: 'forbidden' }],\n    },\n    {\n      code: '<template><Foo /></template>',\n      output: null,\n      options: [['Foo']],\n      errors: [{ messageId: 'forbidden' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-forbidden-elements', rule, {\n  valid: [\n    '<header></header>',\n    '<div></div>',\n    '<footer></footer>',\n    '<p></p>',\n    '<head><meta charset=\"utf-8\"></head>',\n    // Custom forbidden list (script not included).\n    {\n      code: '<script></script>',\n      options: [['html', 'meta', 'style']],\n    },\n    // Object config form.\n    {\n      code: '<script></script>',\n      options: [{ forbidden: ['html', 'meta', 'style'] }],\n    },\n  ],\n  invalid: [\n    // Default config.\n    {\n      code: '<script></script>',\n      output: null,\n      errors: [{ message: 'Use of forbidden element <script>' }],\n    },\n    {\n      code: '<html></html>',\n      output: null,\n      errors: [{ message: 'Use of forbidden element <html>' }],\n    },\n    {\n      code: '<style></style>',\n      output: null,\n      errors: [{ message: 'Use of forbidden element <style>' }],\n    },\n    {\n      code: '<meta charset=\"utf-8\">',\n      output: null,\n      errors: [{ message: 'Use of forbidden element <meta>' }],\n    },\n    {\n      code: '<head><html></html></head>',\n      output: null,\n      errors: [{ message: 'Use of forbidden element <html>' }],\n    },\n    // Custom forbidden list.\n    {\n      code: '<div></div>',\n      output: null,\n      options: [['div']],\n      errors: [{ message: 'Use of forbidden element <div>' }],\n    },\n    {\n      code: '<Foo />',\n      output: null,\n      options: [['Foo']],\n      errors: [{ message: 'Use of forbidden element <Foo>' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-heading-inside-button.js",
    "content": "const rule = require('../../../lib/rules/template-no-heading-inside-button');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-heading-inside-button', rule, {\n  valid: [\n    '<template><button>Click me</button></template>',\n    '<template><h1>Title</h1></template>',\n    '<template><div><h2>Heading</h2></div></template>',\n\n    '<template><button>Show More</button></template>',\n    '<template><button><span>thumbs-up emoji</span>Show More</button></template>',\n    '<template><button><div>Show More</div></button></template>',\n    '<template><div>Showing that it is not a button</div></template>',\n    '<template><div><h1>Page Title in a div is fine</h1></div></template>',\n    '<template><h1>Page Title</h1></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><button><h1>Bad</h1></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><div role=\"button\"><h2>Bad</h2></div></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n\n    {\n      code: '<template><button><h1>Page Title</h1></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><button><h2>Heading Title</h2></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><button><h3>Heading Title</h3></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><button><h4>Heading Title</h4></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><button><h5>Heading Title</h5></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><button><div><h1>Heading Title</h1></div></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><button><h6>Heading Title</h6></button></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n    {\n      code: '<template><div role=\"button\"><h6>Heading in a div with a role of button</h6></div></template>',\n      output: null,\n      errors: [{ messageId: 'noHeading' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-html-comments.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-html-comments');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-html-comments', rule, {\n  valid: [\n    '<template>{{! This is a comment }}</template>',\n    '<template>{{!-- This is a comment --}}</template>',\n    '<template><div>Content</div></template>',\n\n    '<template>{{!-- comment here --}}</template>',\n    '<template>{{!--comment here--}}</template>',\n    '<template>{{!--\\n    line one\\n    line two\\n  --}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template><!-- HTML comment --></template>',\n      output: '<template>{{! HTML comment }}</template>',\n      errors: [{ messageId: 'noHtmlComments' }],\n    },\n\n    {\n      code: '<template><!-- comment here --></template>',\n      output: '<template>{{! comment here }}</template>',\n      errors: [{ messageId: 'noHtmlComments' }],\n    },\n    {\n      code: '<template><!--comment here--></template>',\n      output: '<template>{{!comment here}}</template>',\n      errors: [{ messageId: 'noHtmlComments' }],\n    },\n    {\n      code: '<template><!--\\n    line one\\n    line two\\n  --></template>',\n      output: '<template>{{!\\n    line one\\n    line two\\n  }}</template>',\n      errors: [{ messageId: 'noHtmlComments' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-html-comments', rule, {\n  valid: [\n    '{{!-- comment here --}}',\n    '{{!--comment here--}}',\n    '{{! template-lint-disable no-bare-strings }}',\n    '{{! template-lint-disable }}',\n    '{{!--\\n    line one\\n    line two\\n  --}}',\n  ],\n  invalid: [\n    {\n      code: '<!-- comment here -->',\n      output: '{{! comment here }}',\n      errors: [{ messageId: 'noHtmlComments' }],\n    },\n    {\n      code: '<!--comment here-->',\n      output: '{{!comment here}}',\n      errors: [{ messageId: 'noHtmlComments' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-implicit-this.js",
    "content": "const eslint = require('eslint');\nconst rule = require('../../../lib/rules/template-no-implicit-this');\n\nconst { RuleTester } = eslint;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-implicit-this', rule, {\n  valid: [\n    // Explicit this\n    '<template>{{this.property}}</template>',\n    '<template>{{this.method}}</template>',\n\n    // Named arguments\n    '<template>{{@arg}}</template>',\n    '<template>{{@myArg}}</template>',\n\n    // Built-in helpers\n    '<template>{{yield}}</template>',\n    '<template>{{outlet}}</template>',\n    '<template>{{has-block}}</template>',\n\n    // Named-argument control-flow helpers not flagged\n    '<template>{{if @condition \"yes\" \"no\"}}</template>',\n    '<template>{{each @items}}</template>',\n\n    // SubExpression, modifier, block callees not flagged\n    '<template>{{echo (my-helper @arg)}}</template>',\n    '<template><div {{my-modifier @arg}}></div></template>',\n    '<template>{{#my-component}}{{/my-component}}</template>',\n\n    // Bare {{this}} is not ambiguous\n    '<template>{{this}}</template>',\n\n    // Block params in nested scopes\n    '<template>{{#each @items as |item|}}{{item.name}}{{/each}}</template>',\n\n    // JS scope bindings (imports, const, let, params) are valid references in GJS/GTS\n    `const condition = false;\n     export default <template>{{if condition \"yes\" \"no\"}}</template>;`,\n    `import helper from './my-helper';\n     export default <template>{{helper}}</template>;`,\n    `const items = [1, 2, 3];\n     export default <template>{{#each items as |item|}}{{item}}{{/each}}</template>;`,\n\n    // Components (PascalCase)\n    '<template>{{MyComponent}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{property}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template>{{someValue}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template><div>{{property}}</div></template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n\n    {\n      code: '<template>{{book}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    // Control-flow helper args with no JS binding are still ambiguous\n    {\n      code: '<template>{{if condition \"yes\" \"no\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template>{{book-details}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template>{{book.author}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template>{{helper book}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template>{{#helper book}}{{/helper}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template><MyComponent @prop={{can.do}} /></template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template>{{session.user.name}}</template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: '<template><MyComponent @prop={{session.user.name}} /></template>',\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n    {\n      code: `<template>import { hbs } from 'ember-cli-htmlbars';\n        import { setComponentTemplate } from '@ember/component';\n        import templateOnly from '@ember/component/template-only';\n        export const SomeComponent = setComponentTemplate(hbs\\`{{book}}\\`, templateOnly());</template>`,\n      output: null,\n      errors: [{ messageId: 'noImplicitThis' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-implicit-this', rule, {\n  valid: [\n    // Built-in helpers/keywords\n    '{{yield}}',\n    '{{outlet}}',\n    '{{has-block}}',\n    '{{has-block-params}}',\n    '{{hasBlock}}',\n    '{{hasBlockParams}}',\n    '{{debugger}}',\n    '{{array}}',\n    '{{concat}}',\n    '{{hash}}',\n    '{{log}}',\n    '{{input}}',\n    '{{textarea}}',\n    '{{query-params}}',\n    '{{unique-id}}',\n\n    // Named arguments\n    '{{@book}}',\n    '{{@book.author}}',\n\n    // Explicit this\n    '{{this}}',\n    '{{this.book}}',\n    '{{this.book.author}}',\n\n    // Helpers invoked with arguments\n    '{{helper argument=true}}',\n    '{{some-helper argument=true}}',\n\n    // Helpers invoked with positional arguments (callee is not flagged)\n    '<MyComponent @prop={{can \"edit\" @model}} />',\n\n    // SubExpression callees should not be flagged\n    '{{echo (my-helper @arg)}}',\n    '{{echo (some-util \"value\")}}',\n\n    // ElementModifierStatement callees should not be flagged\n    '<div {{my-modifier @arg}}></div>',\n    '<div {{some-modifier \"value\"}}></div>',\n\n    // BlockStatement callees should not be flagged\n    '{{#my-component}}{{/my-component}}',\n    '{{#some-layout title=\"Hi\"}}content{{/some-layout}}',\n\n    // Block params should be recognized in nested scopes\n    '{{#each @items as |item|}}{{item.name}}{{/each}}',\n    '{{#each @items as |item|}}{{item}}{{/each}}',\n    '{{#let @foo as |bar|}}{{bar.baz}}{{/let}}',\n    '{{#each @items as |item|}}{{#each item.children as |child|}}{{child.name}}{{/each}}{{/each}}',\n\n    // PascalCase components\n    '<WelcomePage />',\n    '<MyComponent @prop={{@value}} />',\n    '{{MyComponent}}',\n\n    // Named arguments in various positions\n    '{{@book argument=true}}',\n    '{{helper argument=@book}}',\n    '{{#helper argument=@book}}{{/helper}}',\n\n    // Explicit this in various positions\n    '{{this.book argument=true}}',\n    '{{helper argument=this.book}}',\n    '{{#helper argument=this.book}}{{/helper}}',\n\n    // Allow config option\n    {\n      code: '{{book-details}}',\n      options: [{ allow: ['book-details'] }],\n    },\n    // Allow config option — regex pattern\n    {\n      code: '{{data-test-foo}}',\n      options: [{ allow: [/^data-test-.+/] }],\n    },\n  ],\n  invalid: [\n    {\n      code: '{{book}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"book\" is not allowed. Use \"@book\" if it is a named argument or \"this.book\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '{{book-details}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"book-details\" is not allowed. Use \"@book-details\" if it is a named argument or \"this.book-details\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '{{book.author}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"book.author\" is not allowed. Use \"@book.author\" if it is a named argument or \"this.book.author\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '{{helper book}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"book\" is not allowed. Use \"@book\" if it is a named argument or \"this.book\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '{{#helper book}}{{/helper}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"book\" is not allowed. Use \"@book\" if it is a named argument or \"this.book\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '<MyComponent @prop={{can.do}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"can.do\" is not allowed. Use \"@can.do\" if it is a named argument or \"this.can.do\" if it is a property on the component.',\n        },\n      ],\n    },\n    // allow: ['can'] should NOT allow 'can.do' (exact match only)\n    {\n      code: '<MyComponent @prop={{can.do}} />',\n      output: null,\n      options: [{ allow: ['can'] }],\n      errors: [\n        {\n          message:\n            'Ambiguous path \"can.do\" is not allowed. Use \"@can.do\" if it is a named argument or \"this.can.do\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '{{session.user.name}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"session.user.name\" is not allowed. Use \"@session.user.name\" if it is a named argument or \"this.session.user.name\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: '<MyComponent @prop={{session.user.name}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"session.user.name\" is not allowed. Use \"@session.user.name\" if it is a named argument or \"this.session.user.name\" if it is a property on the component.',\n        },\n      ],\n    },\n    {\n      code: `import { hbs } from 'ember-cli-htmlbars';\n        import { setComponentTemplate } from '@ember/component';\n        import templateOnly from '@ember/component/template-only';\n        export const SomeComponent = setComponentTemplate(hbs\\`{{book}}\\`, templateOnly());`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Ambiguous path \"book\" is not allowed. Use \"@book\" if it is a named argument or \"this.book\" if it is a property on the component.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-index-component-invocation.js",
    "content": "const rule = require('../../../lib/rules/template-no-index-component-invocation');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-index-component-invocation', rule, {\n  valid: [\n    '<template><Foo::Bar /></template>',\n    '<template><Foo::IndexItem /></template>',\n    '<template><Foo::MyIndex /></template>',\n    '<template><Foo::MyIndex></Foo::MyIndex></template>',\n    '<template>{{foo/index-item}}</template>',\n    '<template>{{foo/my-index}}</template>',\n    '<template>{{foo/bar}}</template>',\n    '<template>{{#foo/bar}}{{/foo/bar}}</template>',\n    '<template>{{component \"foo/bar\"}}</template>',\n    '<template>{{component \"foo/my-index\"}}</template>',\n    '<template>{{component \"foo/index-item\"}}</template>',\n    '<template>{{#component \"foo/index-item\"}}{{/component}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{foo/index}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Replace `{{foo/index ...` to `{{foo ...`',\n        },\n      ],\n    },\n    {\n      code: '<template>{{component \"foo/index\"}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Replace `{{component \"foo/index\" ...` to `{{component \"foo\" ...`',\n        },\n      ],\n    },\n    {\n      code: '<template>{{#foo/index}}{{/foo/index}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Replace `{{#foo/index ...` to `{{#foo ...`',\n        },\n      ],\n    },\n    {\n      code: '<template>{{#component \"foo/index\"}}{{/component}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Replace `{{#component \"foo/index\" ...` to `{{#component \"foo\" ...`',\n        },\n      ],\n    },\n    {\n      code: '<template><Foo::Index /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Replace `<Foo::Index ...` to `<Foo ...`',\n        },\n      ],\n    },\n    {\n      code: '<template><Foo::Index></Foo::Index></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Replace `<Foo::Index ...` to `<Foo ...`',\n        },\n      ],\n    },\n\n    {\n      code: '<template>{{foo/bar (component \"foo/index\")}}</template>',\n      output: null,\n      errors: [{ message: 'Replace `(component \"foo/index\" ...` to `(component \"foo\" ...`' }],\n    },\n    {\n      code: '<template>{{foo/bar name=(component \"foo/index\")}}</template>',\n      output: null,\n      errors: [{ message: 'Replace `(component \"foo/index\" ...` to `(component \"foo\" ...`' }],\n    },\n    {\n      code: '<template><Foo::Bar::Index /></template>',\n      output: null,\n      errors: [{ message: 'Replace `<Foo::Bar::Index ...` to `<Foo::Bar ...`' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-index-component-invocation', rule, {\n  valid: [\n    '<Foo::Bar />',\n    '<Foo::IndexItem />',\n    '<Foo::MyIndex />',\n    '<Foo::MyIndex></Foo::MyIndex>',\n    '{{foo/index-item}}',\n    '{{foo/my-index}}',\n    '{{foo/bar}}',\n    '{{#foo/bar}}{{/foo/bar}}',\n    '{{component \"foo/bar\"}}',\n    '{{component \"foo/my-index\"}}',\n    '{{component \"foo/index-item\"}}',\n    '{{#component \"foo/index-item\"}}{{/component}}',\n  ],\n  invalid: [\n    {\n      code: '{{foo/index}}',\n      output: null,\n      errors: [{ message: 'Replace `{{foo/index ...` to `{{foo ...`' }],\n    },\n    {\n      code: '{{component \"foo/index\"}}',\n      output: null,\n      errors: [{ message: 'Replace `{{component \"foo/index\" ...` to `{{component \"foo\" ...`' }],\n    },\n    {\n      code: '{{#foo/index}}{{/foo/index}}',\n      output: null,\n      errors: [{ message: 'Replace `{{#foo/index ...` to `{{#foo ...`' }],\n    },\n    {\n      code: '{{#component \"foo/index\"}}{{/component}}',\n      output: null,\n      errors: [{ message: 'Replace `{{#component \"foo/index\" ...` to `{{#component \"foo\" ...`' }],\n    },\n    {\n      code: '{{foo/bar (component \"foo/index\")}}',\n      output: null,\n      errors: [{ message: 'Replace `(component \"foo/index\" ...` to `(component \"foo\" ...`' }],\n    },\n    {\n      code: '{{foo/bar name=(component \"foo/index\")}}',\n      output: null,\n      errors: [{ message: 'Replace `(component \"foo/index\" ...` to `(component \"foo\" ...`' }],\n    },\n    {\n      code: '<Foo::Index />',\n      output: null,\n      errors: [{ message: 'Replace `<Foo::Index ...` to `<Foo ...`' }],\n    },\n    {\n      code: '<Foo::Bar::Index />',\n      output: null,\n      errors: [{ message: 'Replace `<Foo::Bar::Index ...` to `<Foo::Bar ...`' }],\n    },\n    {\n      code: '<Foo::Index></Foo::Index>',\n      output: null,\n      errors: [{ message: 'Replace `<Foo::Index ...` to `<Foo ...`' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-inline-event-handlers.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-inline-event-handlers');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-inline-event-handlers', rule, {\n  valid: [\n    `<template>\n      <button {{on \"click\" this.handleClick}}>Click</button>\n    </template>`,\n    `<template>\n      <input {{on \"input\" this.handleInput}} />\n    </template>`,\n    `<template>\n      <div>No handlers</div>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <button onclick=\"alert('test')\">Click</button>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use inline event handlers like \"onclick\". Use the (on) modifier instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div onmousedown=\"handleEvent()\">Content</div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use inline event handlers like \"onmousedown\". Use the (on) modifier instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <form onsubmit=\"return false;\">Form</form>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use inline event handlers like \"onsubmit\". Use the (on) modifier instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-inline-linkto.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-inline-linkto');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-inline-linkto', rule, {\n  valid: [\n    `<template>\n      <LinkTo @route=\"index\">Home</LinkTo>\n    </template>`,\n    `<template>\n      <LinkTo @route=\"about\">\n        About\n      </LinkTo>\n    </template>`,\n    `<template>\n      <div></div>\n    </template>`,\n\n    // GJS/GTS: without an `@ember/routing` import, `<LinkTo>` is a\n    // user-authored component — flagging it would corrupt the user's intent.\n    {\n      filename: 'test.gjs',\n      code: '<template><LinkTo @route=\"index\" /></template>',\n    },\n    {\n      filename: 'test.gts',\n      code: '<template><LinkTo /></template>',\n    },\n\n    // GJS/GTS with the canonical `@ember/routing` import: still allow when\n    // the LinkTo has children (block form).\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"index\">Home</LinkTo></template>',\n    },\n\n    // Renamed import: also allowed when the renamed LinkTo has children.\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo as Link } from \\'@ember/routing\\';\\n<template><Link @route=\"index\">Home</Link></template>',\n    },\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <LinkTo @route=\"index\" />\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Use block form of LinkTo component instead of inline form.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <LinkTo @route=\"about\" />\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Use block form of LinkTo component instead of inline form.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <LinkTo @route=\"contact\"></LinkTo>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Use block form of LinkTo component instead of inline form.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n\n    // GJS/GTS with `@ember/routing` import: childless LinkTo is flagged.\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"index\" /></template>',\n      output: null,\n      errors: [{ messageId: 'noInlineLinkTo', type: 'GlimmerElementNode' }],\n    },\n    {\n      filename: 'test.gts',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"contact\"></LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noInlineLinkTo', type: 'GlimmerElementNode' }],\n    },\n\n    // Renamed import: childless `<Link>` is flagged because it resolves to\n    // the framework `LinkTo`.\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo as Link } from \\'@ember/routing\\';\\n<template><Link @route=\"index\" /></template>',\n      output: null,\n      errors: [{ messageId: 'noInlineLinkTo', type: 'GlimmerElementNode' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-inline-linkto', rule, {\n  valid: [\n    // Block form of curly link-to is OK\n    \"{{#link-to 'routeName' prop}}Link text{{/link-to}}\",\n    \"{{#link-to 'routeName'}}Link text{{/link-to}}\",\n    // Angle bracket with content is OK\n    '<LinkTo @route=\"index\">Home</LinkTo>',\n  ],\n  invalid: [\n    // Inline curly form is not allowed\n    {\n      code: \"{{link-to 'Link text' 'routeName'}}\",\n      output: \"{{#link-to 'routeName'}}Link text{{/link-to}}\",\n      errors: [{ messageId: 'noInlineLinkTo' }],\n    },\n    {\n      code: \"{{link-to 'Link text' 'routeName' one two}}\",\n      output: \"{{#link-to 'routeName' one two}}Link text{{/link-to}}\",\n      errors: [{ messageId: 'noInlineLinkTo' }],\n    },\n    {\n      code: \"{{link-to (concat 'Hello' @username) 'routeName' one two}}\",\n      output: \"{{#link-to 'routeName' one two}}{{concat 'Hello' @username}}{{/link-to}}\",\n      errors: [{ messageId: 'noInlineLinkTo' }],\n    },\n    {\n      code: \"{{link-to 1234 'routeName' one two}}\",\n      output: null,\n      errors: [{ messageId: 'noInlineLinkTo' }],\n    },\n    // Angle bracket with no content\n    {\n      code: '<LinkTo @route=\"index\" />',\n      output: null,\n      errors: [{ messageId: 'noInlineLinkTo' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-inline-styles.js",
    "content": "const rule = require('../../../lib/rules/template-no-inline-styles');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-inline-styles', rule, {\n  valid: [\n    '<template><div class=\"foo\"></div></template>',\n    '<template><div></div></template>',\n    '<template><span></span></template>',\n    '<template><ul class=\"dummy\"></ul></template>',\n    '<template><div style={{foo}}></div></template>',\n    '<template><div style={{html-safe (concat \"background-image: url(\" url \")\")}}></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div style=\"color: red\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInlineStyles' }],\n    },\n    {\n      code: '<template><div style=\"color:blue;margin-left:30px;\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInlineStyles' }],\n    },\n    {\n      // ConcatStatement should be invalid even with allowDynamicStyles (default true)\n      code: '<template><div style=\"{{foo}} bar\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInlineStyles' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-input-block.js",
    "content": "const rule = require('../../../lib/rules/template-no-input-block');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-input-block', rule, {\n  valid: [\n    '<template>{{input value=this.foo}}</template>',\n    '<template>{{button}}</template>',\n    '<template>{{#x-button}}{{/x-button}}</template>',\n    '<template>{{input}}</template>',\n\n    // GJS/GTS: the classic `{{input}}` helper is HBS-only — `input` is not\n    // an ambient strict-mode keyword. Any `{{#input}}` in strict mode is a\n    // user-imported binding (or an unbound name that the strict-mode\n    // compiler will reject on its own); flagging here would corrupt the\n    // user's intent for the imported case.\n    {\n      filename: 'test.gjs',\n      code: '<template>{{#input}}content{{/input}}</template>',\n    },\n    {\n      filename: 'test.gts',\n      code: '<template>{{#input}}content{{/input}}</template>',\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import input from './my-input';\\n<template>{{#input}}content{{/input}}</template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import input from './my-input';\\n<template>{{#input value=this.foo}}{{/input}}</template>\",\n    },\n  ],\n  invalid: [\n    {\n      code: '<template>{{#input}}{{/input}}</template>',\n      output: null,\n      errors: [{ messageId: 'blockUsage' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-input-block', rule, {\n  valid: ['{{button}}', '{{#x-button}}{{/x-button}}', '{{input}}'],\n  invalid: [\n    {\n      code: '{{#input}}{{/input}}',\n      output: null,\n      errors: [{ message: 'Unexpected block usage. The input helper may only be used inline.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-input-tagname.js",
    "content": "const rule = require('../../../lib/rules/template-no-input-tagname');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-input-tagname', rule, {\n  valid: [\n    '<template>{{input value=this.foo}}</template>',\n    '<template>{{input type=\"text\"}}</template>',\n    '<template>{{component \"input\" type=\"text\"}}</template>',\n    '<template>{{yield (component \"input\" type=\"text\")}}</template>',\n    // Rule is disabled in GJS/GTS: `input` is a user-imported binding, not the classic helper\n    { filename: 'test.gjs', code: '<template>{{input tagName=\"span\"}}</template>' },\n    { filename: 'test.gts', code: '<template>{{input tagName=\"foo\"}}</template>' },\n    // GJS/GTS angle-bracket: without an import from @ember/component, <Input> is a user binding\n    { filename: 'test.gjs', code: '<template><Input @tagName=\"button\" /></template>' },\n    {\n      filename: 'test.gjs',\n      code: 'const Input = <template>hi</template>;\\n<template><Input @tagName=\"button\" /></template>',\n    },\n  ],\n  invalid: [\n    {\n      filename: 'test.gjs',\n      code: 'import { Input } from \\'@ember/component\\';\\n<template><Input @tagName=\"button\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      filename: 'test.gts',\n      code: 'import { Input as Field } from \\'@ember/component\\';\\n<template><Field @tagName=\"span\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{input tagName=\"span\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n\n    {\n      code: '<template>{{input tagName=\"foo\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{input tagName=bar}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{component \"input\" tagName=\"foo\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{component \"input\" tagName=bar}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{yield (component \"input\" tagName=\"foo\")}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{yield (component \"input\" tagName=bar)}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-input-tagname', rule, {\n  valid: [\n    '{{input value=foo}}',\n    '{{input type=\"text\"}}',\n    '{{component \"input\" type=\"text\"}}',\n    '{{yield (component \"input\" type=\"text\")}}',\n    '<Input />',\n    '<Input @value={{this.foo}} />',\n  ],\n  invalid: [\n    {\n      code: '<Input @tagName=\"button\" />',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{input tagName=\"span\"}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{input tagName=\"foo\"}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{input tagName=bar}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{component \"input\" tagName=\"foo\"}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{component \"input\" tagName=bar}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{yield (component \"input\" tagName=\"foo\")}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '{{yield (component \"input\" tagName=bar)}}',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-invalid-aria-attributes.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-invalid-aria-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-invalid-aria-attributes', rule, {\n  valid: [\n    '<template><div aria-label=\"Label\">Content</div></template>',\n    '<template><div aria-hidden=\"true\">Content</div></template>',\n    '<template><div aria-describedby=\"id\">Content</div></template>',\n\n    '<template><h1 aria-hidden=\"true\">Valid Heading</h1></template>',\n    '<template><h1 aria-hidden={{true}}>Second valid Heading</h1></template>',\n    '<template><input type=\"email\" aria-required=\"true\" /></template>',\n    '<template><input type=\"text\" aria-labelledby=\"label1 label2\" /></template>',\n    '<template><div role=\"checkbox\" aria-checked=\"true\" onclick=\"handleCheckbox()\" tabindex=\"0\"></div></template>',\n    '<template><button aria-haspopup=\"true\"></button></template>',\n    '<template><button aria-haspopup=\"dialog\"></button></template>',\n    '<template><div role=\"slider\" aria-valuenow=\"50\" aria-valuemax=\"100\" aria-valuemin=\"0\" /></template>',\n    '<template><div role=\"heading\" aria-level={{2}}></div></template>',\n    '<template><input type=\"text\" id=\"name\" aria-invalid=\"grammar\" /></template>',\n    '<template><div role=\"region\" aria-live=\"polite\" aria-relevant=\"additions text\">Valid live region</div></template>',\n    '<template><div aria-label=\"{{@foo.bar}} baz\"></div></template>',\n    '<template><CustomComponent @ariaRequired={{this.ariaRequired}} aria-errormessage=\"errorId\" /></template>',\n    '<template><button type=\"submit\" aria-disabled={{this.isDisabled}}>Submit</button></template>',\n    '<template><div role=\"textbox\" aria-sort={{if this.hasCustomSort \"other\" \"ascending\"}}></div></template>',\n    // Boolean-type attributes with allowundefined: true per aria-query — the\n    // string \"undefined\" is spec-valid (WAI-ARIA 1.2 value tables for these\n    // attrs list true/false/undefined). All 4 share the same code path.\n    '<template><div role=\"combobox\" aria-expanded=\"undefined\"></div></template>',\n    '<template><div aria-hidden=\"undefined\"></div></template>',\n    '<template><div aria-grabbed=\"undefined\" draggable=\"true\"></div></template>',\n    '<template><div role=\"option\" aria-selected=\"undefined\"></div></template>',\n\n    // Token-type aria-orientation lists \"undefined\" in its values array;\n    // passes the natural token check (no special-casing needed).\n    '<template><div role=\"slider\" aria-orientation=\"undefined\"></div></template>',\n    '<template><div role=\"slider\" aria-orientation=\"horizontal\"></div></template>',\n\n    // aria-pressed is tristate WITHOUT allowundefined — string \"undefined\"\n    // is NOT accepted. Explicit valid values still work.\n    '<template><button aria-pressed=\"true\">Toggle</button></template>',\n    '<template><button aria-pressed=\"false\">Toggle</button></template>',\n    '<template><button aria-pressed=\"mixed\">Toggle</button></template>',\n    '<template><button aria-label={{if @isNew (t \"actions.add\") (t \"actions.edit\")}}></button></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div aria-fake=\"value\">Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidAriaAttribute', data: { attribute: 'aria-fake' } }],\n    },\n    {\n      code: '<template><div aria-invalid-attr=\"value\">Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidAriaAttribute', data: { attribute: 'aria-invalid-attr' } }],\n    },\n\n    {\n      code: '<template><input aria-text=\"inaccessible text\" /></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidAriaAttribute' }],\n    },\n    {\n      code: '<template><div role=\"slider\" aria-valuenow={{this.foo}} aria-valuemax={{this.bar}} aria-value-min={{this.baz}} /></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidAriaAttribute' }],\n    },\n    {\n      code: '<template><h1 aria--hidden=\"true\">Broken heading</h1></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidAriaAttribute' }],\n    },\n    {\n      code: '<template><CustomComponent role=\"region\" aria-alert=\"polite\" /></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidAriaAttribute' }],\n    },\n    {\n      code: '<template><span role=\"checkbox\" aria-checked=\"bad-value\" tabindex=\"0\" aria-label=\"Forget me\"></span></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><button type=\"submit\" disabled=\"true\" aria-disabled=\"123\">Submit</button></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><input type=\"text\" disabled=\"true\" aria-errormessage=\"false\" /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><button type=\"submit\" aria-describedby=\"blah false\">Continue at your own risk</button></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div role=\"heading\" aria-level=\"bogus\">Inaccessible heading</div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div role=\"heading\" aria-level=\"true\">Another inaccessible heading</div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div role=\"slider\" aria-valuenow=(2*2)  aria-valuemax=\"100\" aria-valuemin=\"30\">Broken slider</div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div role=\"region\" aria-live=\"no-such-value\">Inaccessible live region</div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div role=\"region\" aria-live=\"polite\" aria-relevant=\"additions errors\">Inaccessible live region</div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><input type=\"text\" aria-required=\"undefined\" /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div aria-label=\"true\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<template><div role=\"slider\" aria-orientation=\"sideways\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    // aria-pressed is tristate WITHOUT allowundefined — string \"undefined\"\n    // is spec-invalid here (aria-query doesn't mark it allowundefined).\n    {\n      code: '<template><button aria-pressed=\"undefined\">Toggle</button></template>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-invalid-aria-attributes', rule, {\n  valid: [\n    '<h1 aria-hidden=\"true\">Valid Heading</h1>',\n    '<h1 aria-hidden={{true}}>Second valid Heading</h1>',\n    '<input type=\"email\" aria-required=\"true\" />',\n    '<input type=\"text\" aria-labelledby=\"label1 label2\" />',\n    '<div role=\"checkbox\" aria-checked=\"true\" onclick=\"handleCheckbox()\" tabindex=\"0\"></div>',\n    '<button aria-haspopup=\"true\"></button>',\n    '<button aria-haspopup=\"dialog\"></button>',\n    '<div role=\"slider\" aria-valuenow=\"50\" aria-valuemax=\"100\" aria-valuemin=\"0\" />',\n    '<div role=\"heading\" aria-level={{2}}></div>',\n    '<input type=\"text\" id=\"name\" aria-invalid=\"grammar\" />',\n    '<div role=\"region\" aria-live=\"polite\" aria-relevant=\"additions text\">Valid live region</div>',\n    '<div aria-label=\"{{@foo.bar}} baz\"></div>',\n    '<CustomComponent @ariaRequired={{this.ariaRequired}} aria-errormessage=\"errorId\" />',\n    '<button type=\"submit\" aria-disabled={{this.isDisabled}}>Submit</button>',\n    '<div role=\"textbox\" aria-sort={{if this.hasCustomSort \"other\" \"ascending\"}}></div>',\n    // Boolean-type attrs with allowundefined (spec-valid \"undefined\" literal):\n    '<div role=\"combobox\" aria-expanded=\"undefined\"></div>',\n    '<div aria-hidden=\"undefined\"></div>',\n    '<div aria-grabbed=\"undefined\" draggable=\"true\"></div>',\n    '<div role=\"option\" aria-selected=\"undefined\"></div>',\n\n    // Token-type aria-orientation — \"undefined\" passes via values list:\n    '<div role=\"slider\" aria-orientation=\"undefined\"></div>',\n    '<div role=\"slider\" aria-orientation=\"horizontal\"></div>',\n\n    // aria-pressed is tristate WITHOUT allowundefined; valid values:\n    '<button aria-pressed=\"true\">Toggle</button>',\n    '<button aria-pressed=\"false\">Toggle</button>',\n    '<button aria-pressed=\"mixed\">Toggle</button>',\n\n    '<button aria-label={{if @isNew (t \"actions.add\") (t \"actions.edit\")}}></button>',\n  ],\n  invalid: [\n    {\n      code: '<input aria-text=\"inaccessible text\" />',\n      output: null,\n      errors: [{ message: 'Invalid ARIA attribute: aria-text' }],\n    },\n    {\n      code: '<div role=\"slider\" aria-valuenow={{this.foo}} aria-valuemax={{this.bar}} aria-value-min={{this.baz}} />',\n      output: null,\n      errors: [{ message: 'Invalid ARIA attribute: aria-value-min' }],\n    },\n    {\n      code: '<h1 aria--hidden=\"true\">Broken heading</h1>',\n      output: null,\n      errors: [{ message: 'Invalid ARIA attribute: aria--hidden' }],\n    },\n    {\n      code: '<CustomComponent role=\"region\" aria-alert=\"polite\" />',\n      output: null,\n      errors: [{ message: 'Invalid ARIA attribute: aria-alert' }],\n    },\n    {\n      code: '<span role=\"checkbox\" aria-checked=\"bad-value\" tabindex=\"0\" aria-label=\"Forget me\"></span>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<button type=\"submit\" disabled=\"true\" aria-disabled=\"123\">Submit</button>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<input type=\"text\" disabled=\"true\" aria-errormessage=\"false\" />',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<button type=\"submit\" aria-describedby=\"blah false\">Continue at your own risk</button>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<div role=\"heading\" aria-level=\"bogus\">Inaccessible heading</div>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<div role=\"heading\" aria-level=\"true\">Another inaccessible heading</div>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<div role=\"slider\" aria-valuenow=(2*2)  aria-valuemax=\"100\" aria-valuemin=\"30\">Broken slider</div>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<div role=\"region\" aria-live=\"no-such-value\">Inaccessible live region</div>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<div role=\"region\" aria-live=\"polite\" aria-relevant=\"additions errors\">Inaccessible live region</div>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<input type=\"text\" aria-required=\"undefined\" />',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    {\n      code: '<div role=\"slider\" aria-orientation=\"sideways\"></div>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n    // aria-pressed has no allowundefined — \"undefined\" is spec-invalid here.\n    {\n      code: '<button aria-pressed=\"undefined\">Toggle</button>',\n      output: null,\n      errors: [{ messageId: 'invalidAriaAttributeValue' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-invalid-interactive.js",
    "content": "const rule = require('../../../lib/rules/template-no-invalid-interactive');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-invalid-interactive', rule, {\n  valid: [\n    {\n      filename: 'test.gjs',\n      code: '<template><button onclick={{this.handleClick}}>Click</button></template>',\n      output: null,\n    },\n    // <a> with href is interactive\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/about\" onclick={{this.handleClick}}>Link</a></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><div role=\"button\" onclick={{this.handleClick}}>Interactive</div></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><div>No handlers</div></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><input onkeydown={{this.handleKey}} /></template>',\n      output: null,\n    },\n\n    '<template><button {{action \"foo\"}}></button></template>',\n    '<template><canvas {{on \"mousedown\"}}></canvas></template>',\n    '<template><div role=\"button\" {{action \"foo\"}}></div></template>',\n    '<template><div randomProperty={{myValue}}></div></template>',\n    '<template><li><button {{action \"foo\"}}></button></li></template>',\n    '<template><form {{action \"foo\" on=\"submit\"}}></form></template>',\n    '<template><form onsubmit={{action \"foo\"}}></form></template>',\n    '<template><form onchange={{action \"foo\"}}></form></template>',\n    '<template><form {{action \"foo\" on=\"reset\"}}></form></template>',\n    '<template><form {{action \"foo\" on=\"change\"}}></form></template>',\n    '<template><form onreset={{action \"foo\"}}></form></template>',\n    '<template><img onerror={{action \"foo\"}}></template>',\n    '<template><img onload={{action \"foo\"}}></template>',\n    '<template><InputSearch @onInput={{action \"foo\"}} /></template>',\n    '<template><InputSearch @onInput={{action \"foo\"}}></InputSearch></template>',\n    '<template>{{#with (hash bar=(component \"foo\")) as |foo|}}<foo.bar @onInput={{action \"foo\"}}></foo.bar>{{/with}}</template>',\n    '<template><form {{on \"submit\" this.send}}></form></template>',\n    '<template><form {{on \"reset\" this.reset}}></form></template>',\n    '<template><form {{on \"change\" this.change}}></form></template>',\n    '<template><div {{on \"scroll\" this.handleScroll}}></div></template>',\n    '<template><code {{on \"copy\" (action @onCopy)}}></code></template>',\n    '<template><img {{on \"load\" this.onLoad}} {{on \"error\" this.onError}}></template>',\n    '<template><select {{on \"change\" this.handleChange}}></select></template>',\n    '<template><details {{on \"toggle\" this.handleToggle}}></details></template>',\n    '<template><video {{on \"pause\" this.onPause}}></video></template>',\n    '<template><img {{action \"foo\" on=\"load\"}}></template>',\n    '<template><img {{action \"foo\" on=\"error\"}}></template>',\n\n    // <summary> is natively interactive\n    '<template><summary onclick={{this.toggle}}>Details</summary></template>',\n\n    // ARIA widget roles: scrollbar, treeitem (+ many others from the shared\n    // interactive-roles util). tooltip is intentionally NOT in the widget\n    // set (per WAI-ARIA 1.2 §5.3.3 it's a document-structure role) and so\n    // handlers on `role=\"tooltip\"` should be flagged — see invalid cases.\n    '<template><div role=\"scrollbar\" onclick={{this.scroll}}>Scroll</div></template>',\n    '<template><div role=\"treeitem\" onclick={{this.select}}>Node</div></template>',\n\n    // audio/video with controls are interactive\n    '<template><audio controls onclick={{this.play}}></audio></template>',\n    '<template><video controls onclick={{this.play}}></video></template>',\n\n    // <canvas> — not in HTML §3.2.5.2.7, but upstream ember-template-lint\n    // treats it as interactive (drawing/game-UI convention); preserved for parity.\n    '<template><canvas onclick={{this.draw}}></canvas></template>',\n\n    // usemap only makes img/object interactive\n    '<template><img usemap=\"#map\" onclick={{this.click}}></template>',\n\n    // Component invocations are skipped (not HTML elements)\n    '<template><@someComponent onclick={{this.click}} /></template>',\n    '<template><this.myComponent onclick={{this.click}} /></template>',\n    '<template><ns.SomeWidget onclick={{this.click}} /></template>',\n\n    // additionalInteractiveTags: tags listed are treated as interactive\n    {\n      code: '<template><div {{on \"click\" this.onClick}}></div></template>',\n      options: [{ additionalInteractiveTags: ['div'] }],\n    },\n    {\n      code: '<template><div {{action \"foo\"}}></div></template>',\n      options: [{ additionalInteractiveTags: ['div'] }],\n    },\n    {\n      code: '<template><div onclick={{action \"foo\"}}></div></template>',\n      options: [{ additionalInteractiveTags: ['div'] }],\n    },\n\n    // ignoredTags: tags listed are skipped entirely\n    {\n      code: '<template><div {{on \"click\" this.actionName}}>...</div></template>',\n      options: [{ ignoredTags: ['div'] }],\n    },\n    {\n      code: '<template><div onclick={{action \"foo\"}}></div></template>',\n      options: [{ ignoredTags: ['div'] }],\n    },\n\n    // Custom elements (hyphenated lowercase) — accepted false negative per #2689.\n    // Their a11y contract is author-defined; ESLint can't introspect.\n    '<template><my-element onclick={{this.handler}}></my-element></template>',\n    '<template><x-foo {{on \"click\" this.handler}}></x-foo></template>',\n  ],\n\n  invalid: [\n    {\n      filename: 'test.gjs',\n      code: '<template><div onclick={{this.handleClick}}>Click me</div></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noInvalidInteractive',\n          data: { tagName: 'div', handler: 'onclick' },\n        },\n      ],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><span onkeydown={{this.handleKey}}>Press key</span></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noInvalidInteractive',\n          data: { tagName: 'span', handler: 'onkeydown' },\n        },\n      ],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><p ondblclick={{this.handleDblClick}}>Double click</p></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noInvalidInteractive',\n          data: { tagName: 'p', handler: 'ondblclick' },\n        },\n      ],\n    },\n\n    {\n      code: '<template><div {{on \"click\" this.actionName}}>...</div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><div {{action \"foo\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><div onclick={{action \"foo\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><div onclick={{pipe-action \"foo\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><div onsubmit={{action \"foo\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><div randomAttribute={{action \"foo\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><form {{action \"foo\" on=\"click\"}}></form></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      code: '<template><div {{action \"foo\" on=\"submit\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      // usemap on non-img/object does NOT make the element interactive\n      code: '<template><div usemap=\"#map\" onclick={{this.click}}>Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      // audio/video without controls is NOT interactive\n      code: '<template><audio onclick={{this.play}}></audio></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidInteractive' }],\n    },\n    {\n      // <a> without href is NOT interactive\n      filename: 'test.gjs',\n      code: '<template><a onclick={{this.handleClick}}>Link</a></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noInvalidInteractive',\n          data: { tagName: 'a', handler: 'onclick' },\n        },\n      ],\n    },\n    {\n      // role=\"tooltip\" is document-structure per WAI-ARIA 1.2 §5.3.3 — NOT\n      // a widget, so a handler on it is as invalid as a handler on a bare div.\n      filename: 'test.gjs',\n      code: '<template><div role=\"tooltip\" onclick={{this.show}}>Tip</div></template>',\n      output: null,\n      errors: [\n        {\n          messageId: 'noInvalidInteractive',\n          data: { tagName: 'div', handler: 'onclick' },\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-invalid-link-text.js",
    "content": "const rule = require('../../../lib/rules/template-no-invalid-link-text');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-invalid-link-text', rule, {\n  valid: [\n    // Link with component child — content is opaque, can't validate.\n    { filename: 'test.gjs', code: '<template><a href=\"/x\"><MyComponent /></a></template>' },\n    { filename: 'test.gjs', code: '<template><a href=\"/x\">prefix <MyComponent /></a></template>' },\n\n    { filename: 'test.gjs', code: '<template><a href=\"/about\">About Us</a></template>' },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\">Click here to read more about this amazing adventure</a></template>',\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\" aria-labelledby=\"some-id\"></a></template>',\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\" aria-label=\"click here to read about our company\"></a></template>',\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\" aria-hidden=\"true\"></a></template>',\n    },\n    { filename: 'test.gjs', code: '<template><a href=\"https://myurl.com\" hidden></a></template>' },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"#\" aria-label={{this.anAriaLabel}}>A link with a variable as aria-label</a></template>',\n    },\n    // In GJS, LinkTo without an import from @ember/routing is not Ember's router link\n    { filename: 'test.gjs', code: '<template><LinkTo>click here</LinkTo></template>' },\n    { filename: 'test.gjs', code: '<template><LinkTo></LinkTo></template>' },\n    // Imported LinkTo with valid text\n    {\n      filename: 'test.gjs',\n      code: \"import { LinkTo } from '@ember/routing'; <template><LinkTo>About Us</LinkTo></template>\",\n    },\n    // allowEmptyLinks: true — empty <a> is valid\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\"></a></template>',\n      options: [{ allowEmptyLinks: true }],\n    },\n    // Dynamic content — can't validate\n    {\n      filename: 'test.gjs',\n      code: \"import { LinkTo } from '@ember/routing'; <template><LinkTo>{{foo}} more</LinkTo></template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: \"import { LinkTo } from '@ember/routing'; <template><LinkTo aria-label={{t 'some-translation'}}>A link with translation</LinkTo></template>\",\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/play\">{{if game.inProgress \"Continue\" \"Start\"}}</a></template>',\n    },\n    {\n      filename: 'test.gjs',\n      code: `\n        import { LinkTo } from '@ember/routing';\n        <template>\n          <LinkTo>\n            {{#if game.inProgress}}\n              Continue\n            {{else}}\n              Start\n            {{/if}}\n          </LinkTo>\n        </template>\n      `,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/play\">{{#if game.inProgress}}Continue{{else}}Start{{/if}}</a></template>',\n    },\n  ],\n\n  invalid: [\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/page\">Click here</a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/page\">More info</a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/page\">Read more</a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // standalone \"more\" is disallowed\n      filename: 'test.gjs',\n      code: '<template><a href=\"/page\">more</a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\"></a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"https://myurl.com\"> </a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a aria-labelledby=\"\" href=\"https://myurl.com\">Click here</a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a aria-label=\"Click here\" href=\"https://myurl.com\">Click here</a></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // Imported LinkTo with disallowed text\n      filename: 'test.gjs',\n      code: \"import { LinkTo } from '@ember/routing'; <template><LinkTo>click here</LinkTo></template>\",\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // Aliased LinkTo import must still be flagged\n      filename: 'test.gjs',\n      code: \"import { LinkTo as RouterLink } from '@ember/routing'; <template><RouterLink>click here</RouterLink></template>\",\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // Imported LinkTo — empty\n      filename: 'test.gjs',\n      code: \"import { LinkTo } from '@ember/routing'; <template><LinkTo></LinkTo></template>\",\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // aria-label with disallowed text overrides content check\n      filename: 'test.gjs',\n      code: 'import { LinkTo } from \\'@ember/routing\\'; <template><LinkTo aria-label=\"click here\">About Us</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><MyLink>click here</MyLink></template>',\n      output: null,\n      options: [{ linkComponents: ['MyLink'] }],\n      errors: [{ messageId: 'invalidText' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-invalid-link-text (hbs)', rule, {\n  valid: [\n    '<a href=\"https://myurl.com\">Click here to read more about this amazing adventure</a>',\n    '{{#link-to}} click here to read more about our company{{/link-to}}',\n    '<LinkTo>Read more about ways semantic HTML can make your code more accessible.</LinkTo>',\n    '<LinkTo>{{foo}} more</LinkTo>',\n    '<a href=\"https://myurl.com\" aria-labelledby=\"some-id\"></a>',\n    '<a href=\"https://myurl.com\" aria-label=\"click here to read about our company\"></a>',\n    '<a href=\"https://myurl.com\" aria-hidden=\"true\"></a>',\n    '<a href=\"https://myurl.com\" hidden></a>',\n    '<LinkTo aria-label={{t \"some-translation\"}}>A link with translation</LinkTo>',\n    '<a href=\"#\" aria-label={{this.anAriaLabel}}>A link with a variable as aria-label</a>',\n    // allowEmptyLinks: true — empty links are valid\n    { code: '<a href=\"https://myurl.com\"></a>', options: [{ allowEmptyLinks: true }] },\n  ],\n  invalid: [\n    {\n      code: '<a href=\"https://myurl.com\">click here</a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // standalone \"more\" is disallowed\n      code: '<a href=\"/page\">more</a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<LinkTo>click here</LinkTo>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '{{#link-to}}click here{{/link-to}}',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<a href=\"https://myurl.com\"></a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<a href=\"https://myurl.com\"> </a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: `<a href=\"https://myurl.com\"> &nbsp;\n</a>`,\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<a aria-labelledby=\"\" href=\"https://myurl.com\">Click here</a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<a aria-labelledby=\" \" href=\"https://myurl.com\">Click here</a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<a aria-label=\"Click here\" href=\"https://myurl.com\">Click here</a>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      // aria-label with disallowed value on LinkTo (text content is valid but aria-label is not)\n      code: '<LinkTo aria-label=\"click here\">About Us</LinkTo>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<LinkTo></LinkTo>',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: `<LinkTo> &nbsp;\n</LinkTo>`,\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '{{#link-to}}{{/link-to}}',\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: `{{#link-to}} &nbsp;\n{{/link-to}}`,\n      output: null,\n      errors: [{ messageId: 'invalidText' }],\n    },\n    {\n      code: '<MyLink>click here</MyLink>',\n      output: null,\n      options: [{ linkComponents: ['MyLink'] }],\n      errors: [{ messageId: 'invalidText' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-invalid-link-title.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-invalid-link-title');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-invalid-link-title', rule, {\n  valid: [\n    // In GJS/GTS, <LinkTo> is only a router link if explicitly imported.\n    // Without an import, it's a user-authored component and the rule shouldn't fire.\n    {\n      filename: 'test.gjs',\n      code: '<template><LinkTo title=\"x\">x</LinkTo></template>',\n    },\n    // With the import, the rule correctly treats it as the router LinkTo.\n    {\n      filename: 'test.gjs',\n      code: `import { LinkTo } from '@ember/routing';\n        <template><LinkTo title=\"More about page\">Page</LinkTo></template>`,\n    },\n\n    '<template><a href=\"/page\" title=\"More information about page\">Page</a></template>',\n    '<template><a href=\"/page\">Page</a></template>',\n    '<template><a href=\"/page\" title={{dynamic}}>Page</a></template>',\n\n    '<template><a href=\"https://myurl.com\">Click here to read more about this amazing adventure</a></template>',\n    '<template>{{#link-to}} click here to read more about our company{{/link-to}}</template>',\n    '<template><LinkTo>Read more about ways semantic HTML can make your code more accessible.</LinkTo></template>',\n    '<template><LinkTo>{{foo}} more</LinkTo></template>',\n    '<template><LinkTo @title=\"nice title\">Something else</LinkTo></template>',\n    '<template><LinkTo title=\"great titles!\">Whatever, don\\'t judge me</LinkTo></template>',\n    '<template><LinkTo title=\"Download the video\">Download</LinkTo></template>',\n    '<template><a href=\"https://myurl.com\" title=\"New to Ember? Read the full tutorial for the best experience\">Read the Tutorial</a></template>',\n    '<template><a href=\"./whatever\" title={{foo}}>Hello!</a></template>',\n    '<template>{{#link-to \"blah.route.here\" title=\"awesome title\"}}Some thing else here{{/link-to}}</template>',\n    `<template>\n      <LinkTo @query={{hash page=@pagination.prevPage}} local-class=\"prev\" @rel=\"prev\" @title=\"previous page\" data-test-pagination-prev>\n        {{svg-jar \"left-pag\"}}\n      </LinkTo>\n    </template>`,\n  ],\n  invalid: [\n    // Imported <LinkTo> in GJS/GTS: rule still applies\n    {\n      filename: 'test.gjs',\n      code: `import { LinkTo } from '@ember/routing';\n        <template><LinkTo title=\"quickstart\">Quickstart</LinkTo></template>`,\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><a href=\"/page\" title=\"\">Page</a></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><a href=\"/page\" title=\"Page\">Page</a></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n\n    {\n      code: '<template><a href=\"https://myurl.com\" title=\"read the tutorial\">Read the Tutorial</a></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><LinkTo title=\"quickstart\">Quickstart</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><LinkTo @title=\"foo\" title=\"blah\">derp</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template>{{#link-to title=\"Do the things\"}}Do the things{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><LinkTo @route=\"some.route\" @title=\"Do the things\">Do the things</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><a href=\"https://myurl.com\" title=\"Tutorial\">Read the Tutorial</a></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template><LinkTo title=\"Tutorial\">Read the Tutorial</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n    {\n      code: '<template>{{#link-to title=\"Tutorial\"}}Read the Tutorial{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noInvalidLinkTitle' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-invalid-link-title', rule, {\n  valid: [\n    '<a href=\"https://myurl.com\">Click here to read more about this amazing adventure</a>',\n    '{{#link-to}} click here to read more about our company{{/link-to}}',\n    '<LinkTo>Read more about ways semantic HTML can make your code more accessible.</LinkTo>',\n    '<LinkTo>{{foo}} more</LinkTo>',\n    '<LinkTo @title=\"nice title\">Something else</LinkTo>',\n    '<LinkTo title=\"great titles!\">Whatever, don\\'t judge me</LinkTo>',\n    '<LinkTo title=\"Download the video\">Download</LinkTo>',\n    '<a href=\"https://myurl.com\" title=\"New to Ember? Read the full tutorial for the best experience\">Read the Tutorial</a>',\n    '<a href=\"./whatever\" title={{foo}}>Hello!</a>',\n    '{{#link-to \"blah.route.here\" title=\"awesome title\"}}Some thing else here{{/link-to}}',\n    `\n      <LinkTo @query={{hash page=@pagination.prevPage}} local-class=\"prev\" @rel=\"prev\" @title=\"previous page\" data-test-pagination-prev>\n        {{svg-jar \"left-pag\"}}\n      </LinkTo>\n    `,\n    '<template><LinkTo>Quickstart</LinkTo></template>',\n  ],\n  invalid: [\n    {\n      code: '<a href=\"https://myurl.com\" title=\"read the tutorial\">Read the Tutorial</a>',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '<LinkTo title=\"quickstart\">Quickstart</LinkTo>',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '<LinkTo @title=\"foo\" title=\"blah\">derp</LinkTo>',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '{{#link-to title=\"Do the things\"}}Do the things{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '<LinkTo @route=\"some.route\" @title=\"Do the things\">Do the things</LinkTo>',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '<a href=\"https://myurl.com\" title=\"Tutorial\">Read the Tutorial</a>',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '<LinkTo title=\"Tutorial\">Read the Tutorial</LinkTo>',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n    {\n      code: '{{#link-to title=\"Tutorial\"}}Read the Tutorial{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Link title attribute should not be the same as link text or empty.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-invalid-meta.js",
    "content": "const rule = require('../../../lib/rules/template-no-invalid-meta');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-invalid-meta', rule, {\n  valid: [\n    '<template><meta charset=\"utf-8\" /></template>',\n    '<template><meta charset=\"UTF-8\" /></template>',\n    '<template><meta charset=\"utf8\" /></template>',\n    '<template><meta name=\"viewport\" content=\"width=device-width\" /></template>',\n\n    // Bare meta (no attrs)\n    '<template><meta></template>',\n\n    // Valid http-equiv refresh (delay=0 for redirect, >72000)\n    '<template><meta http-equiv=\"refresh\" content=\"0; url=http://www.example.com\"></template>',\n    '<template><meta http-equiv=\"refresh\" content=\"72001\"></template>',\n\n    // Dynamic attributes (can't validate at lint time)\n    '<template><meta http-equiv={{httpEquiv}} content={{content}}></template>',\n    '<template><meta name={{name}} content={{content}}></template>',\n\n    // Viewport with user-scalable=yes (valid)\n    '<template><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\"></template>',\n    '<template><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"></template>',\n\n    // property and itemprop with content (valid)\n    '<template><meta property=\"og:type\" content=\"website\"></template>',\n    '<template><meta itemprop=\"type\" content=\"website\"></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><meta charset=\"iso-8859-1\" /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Meta charset should be \"utf-8\". Found: \"iso-8859-1\".',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: '<template><meta charset=\"latin1\" /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Meta charset should be \"utf-8\". Found: \"latin1\".',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: '<template><meta charset=\"windows-1252\" /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Meta charset should be \"utf-8\". Found: \"windows-1252\".',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><meta http-equiv=\"refresh\" content=\"1; url=http://www.example.com\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaRefreshRedirect' }],\n    },\n    {\n      code: '<template><meta http-equiv=\"refresh\" content=\"71999\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaRefreshDelay' }],\n    },\n    {\n      code: '<template><meta name=\"viewport\" content=\"user-scalable=no\"></template>',\n      output: null,\n      errors: [{ messageId: 'viewportUserScalable' }],\n    },\n    {\n      code: '<template><meta name=\"viewport\" content=\"user-scalable = no\"></template>',\n      output: null,\n      errors: [{ messageId: 'viewportUserScalable' }],\n    },\n    {\n      code: '<template><meta name=\"viewport\" content=\"user-scalable= no\"></template>',\n      output: null,\n      errors: [{ messageId: 'viewportUserScalable' }],\n    },\n    {\n      code: '<template><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\"></template>',\n      output: null,\n      errors: [{ messageId: 'viewportMaximumScale' }],\n    },\n    {\n      code: '<template><meta name=\"viewport\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaMissingContent' }],\n    },\n    {\n      code: '<template><meta property=\"og:type\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaMissingContent' }],\n    },\n    {\n      code: '<template><meta itemprop=\"type\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaMissingContent' }],\n    },\n    {\n      code: '<template><meta http-equiv=\"refresh\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaMissingContent' }],\n    },\n    {\n      code: '<template><meta content=\"72001\"></template>',\n      output: null,\n      errors: [{ messageId: 'metaMissingIdentifier' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-invalid-meta', rule, {\n  valid: [\n    '<meta>',\n    '<meta charset=\"UTF-8\">',\n    '<meta http-equiv=\"refresh\" content=\"0; url=http://www.example.com\">',\n    '<meta http-equiv=\"refresh\" content=\"72001\">',\n    '<meta http-equiv={{httpEquiv}} content={{content}}>',\n    '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\">',\n    '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable = yes\">',\n    '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable= yes\">',\n    '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">',\n    '<meta name={{name}} content={{content}}>',\n    '<meta property=\"og:type\" content=\"website\">',\n    '<meta itemprop=\"type\" content=\"website\">',\n    '<div></div>',\n  ],\n  invalid: [\n    {\n      code: '<meta http-equiv=\"refresh\" content=\"1; url=http://www.example.com\">',\n      output: null,\n      errors: [{ message: 'A meta redirect should not have a delay value greater than zero.' }],\n    },\n    {\n      code: '<meta http-equiv=\"refresh\" content=\"71999\">',\n      output: null,\n      errors: [{ message: 'A meta refresh should have a delay greater than 72000 seconds.' }],\n    },\n    {\n      code: '<meta name=\"viewport\" content=\"user-scalable=no\">',\n      output: null,\n      errors: [{ message: 'A meta viewport should not restrict user-scalable.' }],\n    },\n    {\n      code: '<meta name=\"viewport\" content=\"user-scalable = no\">',\n      output: null,\n      errors: [{ message: 'A meta viewport should not restrict user-scalable.' }],\n    },\n    {\n      code: '<meta name=\"viewport\" content=\"user-scalable= no\">',\n      output: null,\n      errors: [{ message: 'A meta viewport should not restrict user-scalable.' }],\n    },\n    {\n      code: '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\">',\n      output: null,\n      errors: [{ message: 'A meta viewport should not set a maximum scale on content.' }],\n    },\n    {\n      code: '<meta name=\"viewport\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'A meta content attribute must be defined if the name, property, itemprop, or http-equiv attribute is defined.',\n        },\n      ],\n    },\n    {\n      code: '<meta property=\"og:type\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'A meta content attribute must be defined if the name, property, itemprop, or http-equiv attribute is defined.',\n        },\n      ],\n    },\n    {\n      code: '<meta itemprop=\"type\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'A meta content attribute must be defined if the name, property, itemprop, or http-equiv attribute is defined.',\n        },\n      ],\n    },\n    {\n      code: '<meta http-equiv=\"refresh\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'A meta content attribute must be defined if the name, property, itemprop, or http-equiv attribute is defined.',\n        },\n      ],\n    },\n    {\n      code: '<meta content=\"72001\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'A meta content attribute cannot be defined if the name, property, itemprop, nor the http-equiv attributes are defined.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-invalid-role.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-invalid-role');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-invalid-role', rule, {\n  valid: [\n    `<template>\n      <div role=\"button\">Click</div>\n    </template>`,\n    `<template>\n      <div role=\"navigation\">Nav</div>\n    </template>`,\n    `<template>\n      <div role=\"main\">Content</div>\n    </template>`,\n    `<template>\n      <div role=\"presentation\">Hidden</div>\n    </template>`,\n    `<template>\n      <div role=\"none\">Hidden</div>\n    </template>`,\n    `<template>\n      <div>No role</div>\n    </template>`,\n    `<template>\n      <div role={{this.dynamicRole}}>Dynamic</div>\n    </template>`,\n\n    '<template><div></div></template>',\n    '<template><div role=\"none\"></div></template>',\n    '<template><div role=\"presentation\"></div></template>',\n    '<template><img alt=\"\" role=\"none\"></template>',\n    '<template><img role=\"none\"></template>',\n    '<template><img alt=\"\" role=\"presentation\"></template>',\n    '<template><img role=\"presentation\"></template>',\n    '<template><span role=\"none\"></span></template>',\n    '<template><span role=\"presentation\"></span></template>',\n    '<template><svg role=\"none\"></svg></template>',\n    '<template><svg role=\"presentation\"></svg></template>',\n    '<template><li role=\"none\"></li></template>',\n    '<template><li role=\"presentation\"></li></template>',\n    '<template><custom-component role=\"none\"></custom-component></template>',\n    '<template><AwesomeThing role=\"none\"></AwesomeThing></template>',\n    '<template><AwesomeThing role=\"presentation\"></AwesomeThing></template>',\n    '<template><table role=\"textbox\"></table></template>',\n    '<template><div role=\"{{if this.inModal \"dialog\" \"contentinfo\" }}\"></div></template>',\n\n    '<template><td role=\"cell\">Data</td></template>',\n\n    // Case-insensitive role matching\n    '<template><div role=\"Button\">Click</div></template>',\n    '<template><div role=\"NAVIGATION\">Nav</div></template>',\n    '<template><div role=\"ALERT\">Alert</div></template>',\n    // catchNonexistentRoles: false — non-existent roles are not flagged\n    {\n      code: '<template><div role=\"command interface\"></div></template>',\n      options: [{ catchNonexistentRoles: false }],\n    },\n\n    // DPUB-ARIA (doc-*) and Graphics-ARIA (graphics-*) are valid per aria-query.\n    '<template><div role=\"doc-abstract\">Abstract</div></template>',\n    '<template><section role=\"doc-chapter\"></section></template>',\n    '<template><svg role=\"graphics-document\"></svg></template>',\n    '<template><svg role=\"graphics-object\"></svg></template>',\n\n    // Whitespace-separated role fallback list — ARIA 1.2 §5.4. Each token\n    // must individually be valid.\n    '<template><div role=\"tabpanel row\"></div></template>',\n    '<template><svg role=\"graphics-document document\"></svg></template>',\n    '<template><section role=\"doc-appendix doc-bibliography\"></section></template>',\n\n    // Role-fallback: `presentation`/`none` in a non-first position does NOT\n    // flag on a semantic element — UAs pick the first recognised role per\n    // §4.1. Here `button` resolves, `presentation` is an unused fallback.\n    '<template><ul role=\"list presentation\"></ul></template>',\n    '<template><table role=\"grid none\"></table></template>',\n\n    // ARIA 1.3 draft roles — not in aria-query 5.3.2 but spec-blessed, so\n    // the rule accepts them via the inline allowlist.\n    '<template><div role=\"associationlist\"></div></template>',\n    '<template><div role=\"associationlistitemkey\"></div></template>',\n    '<template><div role=\"associationlistitemvalue\"></div></template>',\n    '<template><div role=\"comment\"></div></template>',\n    '<template><div role=\"suggestion\"></div></template>',\n  ],\n\n  invalid: [\n    // Common authoring confusion — `datepicker` looks like it could be a\n    // role (it's a UI concept) but isn't in the ARIA registry. Same lookup\n    // path as `role=\"invalid\"`, exercised here for the more likely typo.\n    {\n      code: '<template><div role=\"datepicker\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'invalid' }],\n    },\n    // Empty / whitespace-only role attribute supplies no recognized role\n    // token — flag as an authoring mistake. Aligns with jsx-a11y and\n    // vue-a11y (both flag).\n    {\n      code: '<template><div role=\"\"></div></template>',\n      output: null,\n      errors: [{ messageId: 'invalid' }],\n    },\n    {\n      code: '<template><div role=\"   \"></div></template>',\n      output: null,\n      errors: [{ messageId: 'invalid' }],\n    },\n    {\n      code: `<template>\n        <div role=\"invalid\">Content</div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: \"Invalid ARIA role 'invalid'. Must be a valid ARIA role.\",\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div role=\"btn\">Content</div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: \"Invalid ARIA role 'btn'. Must be a valid ARIA role.\",\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div role=\"fake-role\">Content</div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: \"Invalid ARIA role 'fake-role'. Must be a valid ARIA role.\",\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><ul role=\"presentation\"></ul></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <ul>.' },\n      ],\n    },\n    {\n      code: '<template><ol role=\"presentation\"></ol></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <ol>.' },\n      ],\n    },\n    {\n      code: '<template><table role=\"presentation\"></table></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <table>.' },\n      ],\n    },\n    {\n      code: '<template><table role=\"none\"></table></template>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <table>.' }],\n    },\n    {\n      code: '<template><button role=\"presentation\"></button></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <button>.' },\n      ],\n    },\n    // Role-fallback: unknown leading token is skipped per §4.1, so the\n    // first RECOGNISED role is `presentation` → flag on semantic element.\n    // Uses catchNonexistentRoles: false so the unknown `xxyxyz` doesn't\n    // intercept the check via the invalid-role path.\n    {\n      code: '<template><ul role=\"xxyxyz presentation\"></ul></template>',\n      output: null,\n      options: [{ catchNonexistentRoles: false }],\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <ul>.' },\n      ],\n    },\n    {\n      code: '<template><button role=\"none\"></button></template>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <button>.' }],\n    },\n    {\n      code: '<template><label role=\"presentation\"></label></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <label>.' },\n      ],\n    },\n    {\n      code: '<template><label role=\"none\"></label></template>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <label>.' }],\n    },\n    {\n      code: '<template><div role=\"command interface\"></div></template>',\n      output: null,\n      errors: [{ message: \"Invalid ARIA role 'command'. Must be a valid ARIA role.\" }],\n    },\n    {\n      code: '<template><div role=\"COMMAND INTERFACE\"></div></template>',\n      output: null,\n      // Validation is case-insensitive, but the error message echoes the\n      // author-provided token verbatim so authors see their own text.\n      errors: [{ message: \"Invalid ARIA role 'COMMAND'. Must be a valid ARIA role.\" }],\n    },\n    {\n      code: '<template><div role=\"command interface\"></div></template>',\n      output: null,\n      options: [{ catchNonexistentRoles: true }],\n      errors: [{ message: \"Invalid ARIA role 'command'. Must be a valid ARIA role.\" }],\n    },\n\n    // Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio\n    {\n      code: '<template><iframe role=\"presentation\"></iframe></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <iframe>.' },\n      ],\n    },\n    {\n      code: '<template><video role=\"none\"></video></template>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <video>.' }],\n    },\n    {\n      code: '<template><audio role=\"presentation\"></audio></template>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <audio>.' },\n      ],\n    },\n    {\n      code: '<template><embed role=\"none\"></template>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <embed>.' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-invalid-role', rule, {\n  valid: [\n    '<div></div>',\n    '<div role=\"none\"></div>',\n    '<div role=\"presentation\"></div>',\n    '<img alt=\"\" role=\"none\">',\n    '<img role=\"none\">',\n    '<img alt=\"\" role=\"presentation\">',\n    '<img role=\"presentation\">',\n    '<span role=\"none\"></span>',\n    '<span role=\"presentation\"></span>',\n    '<svg role=\"none\"></svg>',\n    '<svg role=\"presentation\"></svg>',\n    '<li role=\"none\"></li>',\n    '<li role=\"presentation\"></li>',\n    '<custom-component role=\"none\"></custom-component>',\n    '<AwesomeThing role=\"none\"></AwesomeThing>',\n    '<AwesomeThing role=\"presentation\"></AwesomeThing>',\n    '<table role=\"textbox\"></table>',\n    '<div role=\"{{if this.inModal \"dialog\" \"contentinfo\" }}\"></div>',\n    '<td role=\"cell\">Data</td>',\n    // Case-insensitive role matching\n    '<div role=\"Button\">Click</div>',\n    '<div role=\"NAVIGATION\">Nav</div>',\n    '<div role=\"ALERT\">Alert</div>',\n    // catchNonexistentRoles: false — non-existent roles are not flagged\n    {\n      code: '<div role=\"command interface\"></div>',\n      options: [{ catchNonexistentRoles: false }],\n    },\n\n    // DPUB-ARIA (doc-*) and Graphics-ARIA (graphics-*) roles.\n    '<div role=\"doc-abstract\">Abstract</div>',\n    '<section role=\"doc-chapter\"></section>',\n    '<svg role=\"graphics-document\"></svg>',\n\n    // Whitespace-separated role fallback list.\n    '<div role=\"tabpanel row\"></div>',\n    '<svg role=\"graphics-document document\"></svg>',\n    '<section role=\"doc-appendix doc-bibliography\"></section>',\n\n    // ARIA 1.3 draft roles — not in aria-query 5.3.2 but spec-blessed, so\n    // the rule accepts them via the inline allowlist.\n    '<div role=\"associationlist\"></div>',\n    '<div role=\"associationlistitemkey\"></div>',\n    '<div role=\"associationlistitemvalue\"></div>',\n    '<div role=\"comment\"></div>',\n    '<div role=\"suggestion\"></div>',\n  ],\n  invalid: [\n    {\n      code: '<ul role=\"presentation\"></ul>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <ul>.' },\n      ],\n    },\n    {\n      code: '<ol role=\"presentation\"></ol>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <ol>.' },\n      ],\n    },\n    {\n      code: '<table role=\"presentation\"></table>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <table>.' },\n      ],\n    },\n    {\n      code: '<table role=\"none\"></table>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <table>.' }],\n    },\n    {\n      code: '<button role=\"presentation\"></button>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <button>.' },\n      ],\n    },\n    {\n      code: '<button role=\"none\"></button>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <button>.' }],\n    },\n    {\n      code: '<label role=\"presentation\"></label>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <label>.' },\n      ],\n    },\n    {\n      code: '<label role=\"none\"></label>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <label>.' }],\n    },\n    {\n      code: '<div role=\"command interface\"></div>',\n      output: null,\n      errors: [{ message: \"Invalid ARIA role 'command'. Must be a valid ARIA role.\" }],\n    },\n    {\n      code: '<div role=\"command interface\"></div>',\n      output: null,\n      options: [{ catchNonexistentRoles: true }],\n      errors: [{ message: \"Invalid ARIA role 'command'. Must be a valid ARIA role.\" }],\n    },\n    {\n      code: '<div role=\"COMMAND INTERFACE\"></div>',\n      output: null,\n      // Validation is case-insensitive, but the error message echoes the\n      // author-provided token verbatim so authors see their own text.\n      errors: [{ message: \"Invalid ARIA role 'COMMAND'. Must be a valid ARIA role.\" }],\n    },\n    // Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio, embed\n    {\n      code: '<iframe role=\"presentation\"></iframe>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <iframe>.' },\n      ],\n    },\n    {\n      code: '<video role=\"none\"></video>',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <video>.' }],\n    },\n    {\n      code: '<audio role=\"presentation\"></audio>',\n      output: null,\n      errors: [\n        { message: 'The role \"presentation\" should not be used on the semantic element <audio>.' },\n      ],\n    },\n    {\n      code: '<embed role=\"none\">',\n      output: null,\n      errors: [{ message: 'The role \"none\" should not be used on the semantic element <embed>.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-jsx-attributes.js",
    "content": "const rule = require('../../../lib/rules/template-no-jsx-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-jsx-attributes', rule, {\n  valid: [\n    '<template><div></div></template>',\n    '<template><div class=\"foo\"></div></template>',\n    '<template><div class></div></template>',\n    '<template><div autoplay></div></template>',\n    '<template><div contenteditable=\"true\"></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div acceptCharset=\"utf-8\"></div></template>',\n      output: '<template><div accept-charset=\"utf-8\"></div></template>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"acceptCharset\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n    {\n      code: '<template><div contentEditable=\"true\"></div></template>',\n      output: '<template><div contenteditable=\"true\"></div></template>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"contentEditable\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n    {\n      code: '<template><div className></div></template>',\n      output: '<template><div class></div></template>',\n      errors: [\n        {\n          message:\n            \"Attribute, className, does not assign the 'class' attribute as it would in JSX.  To assign the 'class' attribute, set the 'class' attribute, instead of 'className'. In HTML, all attributes are valid, but 'className' doesn't do anything.\",\n        },\n      ],\n    },\n    {\n      code: '<template><div className=\"foo\"></div></template>',\n      output: '<template><div class=\"foo\"></div></template>',\n      errors: [\n        {\n          message:\n            \"Attribute, className, does not assign the 'class' attribute as it would in JSX.  To assign the 'class' attribute, set the 'class' attribute, instead of 'className'. In HTML, all attributes are valid, but 'className' doesn't do anything.\",\n        },\n      ],\n    },\n\n    {\n      code: '<template><div autoPlay></div></template>',\n      output: '<template><div autoplay></div></template>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"autoPlay\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n    {\n      code: '<template><div contentEditable></div></template>',\n      output: '<template><div contenteditable></div></template>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"contentEditable\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-jsx-attributes', rule, {\n  valid: [\n    '<div></div>',\n    '<div class=\"foo\"></div>',\n    '<div class></div>',\n    '<div autoplay></div>',\n    '<div contenteditable=\"true\"></div>',\n  ],\n  invalid: [\n    {\n      code: '<div acceptCharset=\"utf-8\"></div>',\n      output: '<div accept-charset=\"utf-8\"></div>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"acceptCharset\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n    {\n      code: '<div contentEditable=\"true\"></div>',\n      output: '<div contenteditable=\"true\"></div>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"contentEditable\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n    {\n      code: '<div className></div>',\n      output: '<div class></div>',\n      errors: [\n        {\n          message:\n            \"Attribute, className, does not assign the 'class' attribute as it would in JSX.  To assign the 'class' attribute, set the 'class' attribute, instead of 'className'. In HTML, all attributes are valid, but 'className' doesn't do anything.\",\n        },\n      ],\n    },\n    {\n      code: '<div className=\"foo\"></div>',\n      output: '<div class=\"foo\"></div>',\n      errors: [\n        {\n          message:\n            \"Attribute, className, does not assign the 'class' attribute as it would in JSX.  To assign the 'class' attribute, set the 'class' attribute, instead of 'className'. In HTML, all attributes are valid, but 'className' doesn't do anything.\",\n        },\n      ],\n    },\n    {\n      code: '<div autoPlay></div>',\n      output: '<div autoplay></div>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"autoPlay\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n    {\n      code: '<div contentEditable></div>',\n      output: '<div contenteditable></div>',\n      errors: [\n        {\n          message:\n            'Incorrect html attribute name detected - \"contentEditable\", is probably unintended. Attributes in HTML are kebeb case.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-let-reference.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-let-reference');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-let-reference', rule, {\n  valid: [\n    `\n      const a = '';\n      function create(d) {\n      <template>\n        <Abc as |x a|>\n          {{x}}\n          {{a}}\n        </Abc>\n      {{a}}\n      {{d}}\n      {{this.f}}\n      </template>\n      }\n    `,\n    `\n      const a = '';\n      <template>\n      <a></a>\n      </template>\n    `,\n    // make sure rule does not error out on missing references\n    `\n      const a = '';\n      <template>\n      {{ab}}\n      <ab></ab>\n      </template>\n    `,\n  ],\n\n  invalid: [\n    {\n      code: `\n      let a = '';\n      <template>\n      {{a}}\n      </template>\n      `,\n      output: null,\n      errors: [{ type: 'VarHead', message: rule.meta.messages['no-let'] }],\n    },\n    {\n      code: `\n      var a = '';\n      <template>\n      <a></a>\n      </template>\n      `,\n      output: null,\n      errors: [{ type: 'GlimmerElementNodePart', message: rule.meta.messages['no-let'] }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-link-to-positional-params.js",
    "content": "const rule = require('../../../lib/rules/template-no-link-to-positional-params');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-link-to-positional-params', rule, {\n  valid: [\n    {\n      filename: 'test.gjs',\n      code: '<template><LinkTo @route=\"index\">Home</LinkTo></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><LinkTo @route=\"posts.post\" @model={{this.post}}>Post</LinkTo></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/home\">Home</a></template>',\n      output: null,\n    },\n\n    '<template>{{#link-to route=\"about\"}}About Us{{/link-to}}</template>',\n    '<template>{{#link-to route=\"post\" model=@post}}Read {{@post.title}}...{{/link-to}}</template>',\n    `<template>{{#link-to route=\"post.comment\" models=(array post comment)}}\n        Comment by {{comment.author.name}} on {{comment.date}}\n      {{/link-to}}</template>`,\n    `<template>{{#link-to route=\"posts\" query=(hash direction=\"desc\" showArchived=false)}}\n        Recent Posts\n      {{/link-to}}</template>`,\n    '<template><LinkTo @route=\"about\">About Us</LinkTo></template>',\n    '<template><LinkTo @route=\"post\" @model={{@post}}>Read {{@post.title}}...</LinkTo></template>',\n    `<template><LinkTo @route=\"post.comment\" @models={{array post comment}}>\n        Comment by {{comment.author.name}} on {{comment.date}}\n      </LinkTo></template>`,\n    `<template><LinkTo @route=\"posts\" @query={{hash direction=\"desc\" showArchived=false}}>\n        Recent Posts\n      </LinkTo></template>`,\n  ],\n\n  invalid: [\n    // Note: This rule is simplified and may need adjustment based on actual LinkTo usage patterns\n    // The real eslint-plugin-ember rule has more complex logic for detecting positional params\n\n    {\n      code: '<template>{{link-to \"About Us\" \"about\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{link-to \"About Us\" (if this.showNewAboutPage \"about-us\" \"about\")}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{link-to (t \"about\") \"about\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{link-to (t \"about\") this.aboutRoute}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{link-to (t \"about\") this.aboutRoute \"foo\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{link-to (t \"about\") this.aboutRoute \"foo\" \"bar\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{link-to (t \"about\") this.aboutRoute \"foo\" \"bar\" (query-params foo=\"bar\")}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to (if this.showNewAboutPage \"about-us\" \"about\")}}About Us{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to \"about\"}}About Us{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to this.aboutRoute}}About Us{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to this.aboutRoute \"foo\"}}About Us{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to this.aboutRoute \"foo\" \"bar\"}}About Us{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to this.aboutRoute \"foo\" \"bar\" (query-params foo=\"bar\")}}About Us{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: '<template>{{#link-to \"post\" @post}}Read {{@post.title}}...{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: `<template>{{#link-to \"post.comment\" @comment.post @comment}}\n        Comment by {{@comment.author.name}} on {{@comment.date}}\n      {{/link-to}}</template>`,\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n    {\n      code: `<template>{{#link-to \"posts\" (query-params direction=\"desc\" showArchived=false)}}\n        Recent Posts\n      {{/link-to}}</template>`,\n      output: null,\n      errors: [{ messageId: 'noLinkToPositionalParams' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-link-to-positional-params', rule, {\n  valid: [\n    '{{#link-to route=\"about\"}}About Us{{/link-to}}',\n    '{{#link-to route=\"post\" model=@post}}Read {{@post.title}}...{{/link-to}}',\n    `{{#link-to route=\"post.comment\" models=(array post comment)}}\n        Comment by {{comment.author.name}} on {{comment.date}}\n      {{/link-to}}`,\n    `{{#link-to route=\"posts\" query=(hash direction=\"desc\" showArchived=false)}}\n        Recent Posts\n      {{/link-to}}`,\n    '<LinkTo @route=\"about\">About Us</LinkTo>',\n    '<LinkTo @route=\"post\" @model={{@post}}>Read {{@post.title}}...</LinkTo>',\n    `<LinkTo @route=\"post.comment\" @models={{array post comment}}>\n        Comment by {{comment.author.name}} on {{comment.date}}\n      </LinkTo>`,\n    `<LinkTo @route=\"posts\" @query={{hash direction=\"desc\" showArchived=false}}>\n        Recent Posts\n      </LinkTo>`,\n  ],\n  invalid: [\n    {\n      code: '{{link-to \"About Us\" \"about\"}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{link-to \"About Us\" (if this.showNewAboutPage \"about-us\" \"about\")}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{link-to (t \"about\") \"about\"}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{link-to (t \"about\") this.aboutRoute}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{link-to (t \"about\") this.aboutRoute \"foo\"}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{link-to (t \"about\") this.aboutRoute \"foo\" \"bar\"}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{link-to (t \"about\") this.aboutRoute \"foo\" \"bar\" (query-params foo=\"bar\")}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to (if this.showNewAboutPage \"about-us\" \"about\")}}About Us{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to \"about\"}}About Us{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to this.aboutRoute}}About Us{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to this.aboutRoute \"foo\"}}About Us{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to this.aboutRoute \"foo\" \"bar\"}}About Us{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to this.aboutRoute \"foo\" \"bar\" (query-params foo=\"bar\")}}About Us{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: '{{#link-to \"post\" @post}}Read {{@post.title}}...{{/link-to}}',\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: `{{#link-to \"post.comment\" @comment.post @comment}}\n        Comment by {{@comment.author.name}} on {{@comment.date}}\n      {{/link-to}}`,\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n    {\n      code: `{{#link-to \"posts\" (query-params direction=\"desc\" showArchived=false)}}\n        Recent Posts\n      {{/link-to}}`,\n      output: null,\n      errors: [{ message: 'Positional params in LinkTo are deprecated. Use @route instead.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-link-to-tagname.js",
    "content": "const rule = require('../../../lib/rules/template-no-link-to-tagname');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-link-to-tagname', rule, {\n  valid: [\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"index\">Home</LinkTo></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><link-to @route=\"about\">About</link-to></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gjs',\n      code: '<template><a href=\"/home\">Home</a></template>',\n      output: null,\n    },\n\n    // User-authored LinkTo component (no @ember/routing import) — should NOT be flagged\n    {\n      filename: 'test.gjs',\n      code: '<template><LinkTo @tagName=\"button\">Home</LinkTo></template>',\n      output: null,\n    },\n    {\n      filename: 'test.gts',\n      code: '<template><LinkTo @tagName=\"button\">Home</LinkTo></template>',\n      output: null,\n    },\n\n    // Bare tagName (without @) is just an HTML attribute, not flagged\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"index\" tagName=\"button\">Home</LinkTo></template>',\n      output: null,\n    },\n    '<template><Foo @route=\"routeName\" @tagName=\"button\">Link text</Foo></template>',\n    '<template><LinkTo @route=\"routeName\">Link text</LinkTo></template>',\n    '<template>{{#link-to \"routeName\"}}Link text{{/link-to}}</template>',\n    '<template>{{#foo \"routeName\" tagName=\"button\"}}Link text{{/foo}}</template>',\n    '<template>{{link-to \"Link text\" \"routeName\"}}</template>',\n    '<template>{{foo \"Link text\" \"routeName\" tagName=\"button\"}}</template>',\n  ],\n\n  invalid: [\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"about\" @tagName=\"span\">About</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n    {\n      filename: 'test.gts',\n      code: 'import { LinkTo } from \\'@ember/routing\\';\\n<template><LinkTo @route=\"about\" @tagName=\"span\">About</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n    // Renamed import\n    {\n      filename: 'test.gjs',\n      code: 'import { LinkTo as Link } from \\'@ember/routing\\';\\n<template><Link @tagName=\"button\">x</Link></template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n    {\n      filename: 'test.gts',\n      code: 'import { LinkTo as Link } from \\'@ember/routing\\';\\n<template><Link @route=\"index\" @tagName=\"button\">x</Link></template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n\n    {\n      code: '<template><LinkTo @route=\"routeName\" @tagName=\"button\">Link text</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n    {\n      code: '<template>{{#link-to \"routeName\" tagName=\"button\"}}Link text{{/link-to}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n    {\n      code: '<template>{{link-to \"Link text\" \"routeName\" tagName=\"button\"}}</template>',\n      output: null,\n      errors: [{ messageId: 'noLinkToTagname' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-link-to-tagname', rule, {\n  valid: [\n    // Bare tagName (without @) is just an HTML attribute, not flagged\n    '<LinkTo @route=\"index\" tagName=\"button\">Home</LinkTo>',\n    '<Foo @route=\"routeName\" @tagName=\"button\">Link text</Foo>',\n    '<LinkTo @route=\"routeName\">Link text</LinkTo>',\n    '{{#link-to \"routeName\"}}Link text{{/link-to}}',\n    '{{#foo \"routeName\" tagName=\"button\"}}Link text{{/foo}}',\n    '{{link-to \"Link text\" \"routeName\"}}',\n    '{{foo \"Link text\" \"routeName\" tagName=\"button\"}}',\n  ],\n  invalid: [\n    {\n      code: '<LinkTo @route=\"routeName\" @tagName=\"button\">Link text</LinkTo>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',\n        },\n      ],\n    },\n    {\n      code: '<link-to @route=\"contact\" @tagName=\"div\">Contact</link-to>',\n      output: null,\n      errors: [\n        {\n          message:\n            '@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',\n        },\n      ],\n    },\n    {\n      code: '{{#link-to \"routeName\" tagName=\"button\"}}Link text{{/link-to}}',\n      output: null,\n      errors: [\n        {\n          message:\n            '@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',\n        },\n      ],\n    },\n    {\n      code: '{{link-to \"Link text\" \"routeName\" tagName=\"button\"}}',\n      output: null,\n      errors: [\n        {\n          message:\n            '@tagName on <LinkTo> is not supported (removed in Ember 4.0). <LinkTo> always renders an <a> element.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-log.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-log');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-log', rule, {\n  valid: [\n    `<template>\n      <div>Hello World</div>\n    </template>`,\n    `<template>\n      {{this.log}}\n    </template>`,\n    `<template>\n      {{logger}}\n    </template>`,\n    `<template>\n      <div data-test-log={{true}}></div>\n    </template>`,\n    // Block param: log as a yielded value should not be flagged\n    `<template>\n      {{#each this.logs as |log|}}{{log}}{{/each}}\n    </template>`,\n    `<template>\n      {{#let this.log as |log|}}{{log}}{{/let}}\n    </template>`,\n    `<template>\n      {{#let (component \"my-log-component\") as |log|}}{{#log}}message{{/log}}{{/let}}\n    </template>`,\n    `<template>\n      <Logs @logs={{this.logs}} as |log|>{{log}}</Logs>\n    </template>`,\n    `<template>\n      <Logs @logs={{this.logs}} as |log|><Log>{{log}}</Log></Logs>\n    </template>`,\n    // GJS/GTS: imported JS bindings are not flagged\n    `import log from './my-log';\nexport default <template>{{log \"hi\"}}</template>;`,\n    `import log from '@company/logger';\nexport default <template>{{log this.message}}</template>;`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        {{log \"debug message\"}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected log statement in template.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{#if condition}}\n          {{log this.value}}\n        {{/if}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected log statement in template.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{#log \"test\"}}\n          content\n        {{/log}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected log statement in template.',\n          type: 'GlimmerBlockStatement',\n        },\n      ],\n    },\n    // log helper used inside a block that does NOT shadow it\n    {\n      code: `<template>\n        {{#each this.messages as |message|}}{{log message}}{{/each}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected log statement in template.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        {{#let this.message as |message|}}{{log message}}{{/let}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected log statement in template.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <Messages @messages={{this.messages}} as |message|>{{#log}}{{message}}{{/log}}</Messages>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Unexpected log statement in template.',\n          type: 'GlimmerBlockStatement',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-model-argument-in-route-templates.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-model-argument-in-route-templates');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-model-argument-in-route-templates', rule, {\n  valid: [\n    {\n      filename: 'app/templates/index.hbs',\n      code: '<template>{{this.model}}</template>',\n    },\n    {\n      filename: 'app/templates/users.hbs',\n      code: '<template>{{@data}}</template>',\n    },\n    {\n      filename: 'app/components/user-card.gjs',\n      code: '<template>{{@model}}</template>',\n    },\n    // Partial templates (basename starts with '-') are skipped.\n    {\n      filename: 'app/templates/-partial.hbs',\n      code: '<template>{{@model.foo}}</template>',\n    },\n\n    '<template>{{model}}</template>',\n    '<template>{{@modelythingy}}</template>',\n  ],\n  invalid: [\n    {\n      filename: 'app/templates/index.hbs',\n      code: '<template>{{@model}}</template>',\n      output: '<template>{{this.model}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n    {\n      filename: 'app/templates/users.hbs',\n      code: '<template>{{@model.name}}</template>',\n      output: '<template>{{this.model.name}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n    {\n      filename: 'app/templates/posts/show.hbs',\n      code: '<template>{{@model.id}}</template>',\n      output: '<template>{{this.model.id}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n\n    {\n      filename: 'app/templates/index.hbs',\n      code: '<template>{{@model.foo}}</template>',\n      output: '<template>{{this.model.foo}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n    {\n      filename: 'app/templates/index.hbs',\n      code: '<template>{{@model.foo.bar}}</template>',\n      output: '<template>{{this.model.foo.bar}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n    // .gjs route templates are also linted (not gated to .hbs).\n    {\n      filename: 'app/routes/posts.gjs',\n      code: '<template>{{@model.foo}}</template>',\n      output: '<template>{{this.model.foo}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n    {\n      code: '<template>{{@model.foo}}</template>',\n      output: '<template>{{this.model.foo}}</template>',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-model-argument-in-route-templates', rule, {\n  valid: [\n    '{{model}}',\n    '{{this.model}}',\n    '{{@modelythingy}}',\n    // Component templates are not routes.\n    {\n      filename: 'app/components/user-card.hbs',\n      code: '{{@model}}',\n    },\n    // Partials (basename starts with '-') are not routes.\n    {\n      filename: 'app/templates/-partial.hbs',\n      code: '{{@model.foo}}',\n    },\n  ],\n  invalid: [\n    // Unknown path defaults to lint.\n    {\n      code: '{{@model}}',\n      output: '{{this.model}}',\n      errors: [{ messageId: 'noModelArgumentInRouteTemplates' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-multiple-empty-lines.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-multiple-empty-lines');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-multiple-empty-lines', rule, {\n  valid: [\n    `<template>\n      <div>Hello</div>\n\n      <div>World</div>\n    </template>`,\n    {\n      code: `<template>\n      <div>Hello</div>\n\n\n      <div>World</div>\n    </template>`,\n      options: [{ max: 2 }],\n    },\n    `<template>\n      <div>Content</div>\n    </template>`,\n\n    '<template><div>foo</div><div>bar</div></template>',\n    `<template><div>foo</div>\n<div>bar</div></template>`,\n    `<template><div>foo</div>r\n<div>bar</div></template>`,\n    `<template><div>foo</div>\n\n<div>bar</div></template>`,\n    `<template><div>foo</div>r\nr\n<div>bar</div></template>`,\n    `<template>\n<div>foo</div>\n\n<div>bar</div>\n</template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n      <div>Hello</div>\n\n\n      <div>World</div>\n    </template>`,\n      output: `<template>\n      <div>Hello</div>\n\n      <div>World</div>\n    </template>`,\n      errors: [\n        {\n          message: 'More than 1 blank line not allowed.',\n        },\n      ],\n    },\n    {\n      code: `<template>\n      <div>First</div>\n\n\n\n      <div>Second</div>\n    </template>`,\n      output: `<template>\n      <div>First</div>\n\n      <div>Second</div>\n    </template>`,\n      errors: [\n        {\n          message: 'More than 1 blank line not allowed.',\n        },\n      ],\n    },\n\n    {\n      code: `<template><div>foo</div>\n\n\n<div>bar</div></template>`,\n      output: `<template><div>foo</div>\n\n<div>bar</div></template>`,\n      errors: [{ message: 'More than 1 blank line not allowed.' }],\n    },\n    {\n      code: `<template><div>foo</div>\n\n\n\n\n<div>bar</div></template>`,\n      output: `<template><div>foo</div>\n\n<div>bar</div></template>`,\n      errors: [{ message: 'More than 1 blank line not allowed.' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-multiple-empty-lines', rule, {\n  valid: [\n    '<div>foo</div><div>bar</div>',\n    `<div>foo</div>\n<div>bar</div>`,\n    `<div>foo</div>\n\n<div>bar</div>`,\n    `\n<div>foo</div>\n\n<div>bar</div>\n`,\n    '<div>foo</div>\\r\\n<div>bar</div>',\n    '<div>foo</div>\\r\\n\\r\\n<div>bar</div>',\n    {\n      code: '<div>foo</div>\\n\\n\\n<div>bar</div>',\n      options: [{ max: 2 }],\n    },\n    {\n      code: '<div>foo</div>\\r\\n\\r\\n\\r\\n<div>bar</div>',\n      options: [{ max: 2 }],\n    },\n  ],\n  invalid: [\n    {\n      code: `<div>foo</div>\n\n\n<div>bar</div>`,\n      output: `<div>foo</div>\n\n<div>bar</div>`,\n      errors: [{ message: 'More than 1 blank line not allowed.' }],\n    },\n    {\n      code: `<div>foo</div>\n\n\n\n\n<div>bar</div>`,\n      output: `<div>foo</div>\n\n<div>bar</div>`,\n      errors: [{ message: 'More than 1 blank line not allowed.' }],\n    },\n    {\n      code: '<div>foo</div>\\n\\n\\n\\n\\n<div>bar</div>',\n      output: '<div>foo</div>\\n\\n\\n\\n<div>bar</div>',\n      options: [{ max: 3 }],\n      errors: [{ message: 'More than 3 blank lines not allowed.' }],\n    },\n    // loc fix: the excess empty line (line 3) is reported at the correct location\n    {\n      code: '<div>foo</div>\\n\\n\\n<div>bar</div>',\n      output: '<div>foo</div>\\n\\n<div>bar</div>',\n      errors: [{ message: 'More than 1 blank line not allowed.', line: 3, endLine: 4 }],\n    },\n    // Trailing empty lines\n    {\n      code: '<div>foo</div>\\n\\n\\n',\n      output: '<div>foo</div>\\n\\n',\n      errors: [{ message: 'More than 1 blank line not allowed.' }],\n    },\n    {\n      code: '<div>foo</div>\\n\\n\\n\\n\\n',\n      output: '<div>foo</div>\\n\\n',\n      errors: [{ message: 'More than 1 blank line not allowed.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-mut-helper.js",
    "content": "const rule = require('../../../lib/rules/template-no-mut-helper');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-mut-helper', rule, {\n  valid: [\n    '<template><Input @value={{this.name}} @onChange={{this.updateName}} /></template>',\n    '<template>{{this.mut}}</template>',\n    '<template>{{@mut}}</template>',\n    '<template>{{set this \"property\" value}}</template>',\n\n    '<template><MyComponent @toggled={{this.showAggregatedLine}}/></template>',\n    '<template><MyComponent @toggle={{set this \"isDropdownOpen\"}}/></template>',\n    '<template><MyComponent @onFocusOut={{action \"onFocusOutKeySkillsInput\" value=\"target.value\"}}/></template>',\n    '<template><MyComponent {{on \"click\" (set this \"isDropdownOpen\" false)}}/></template>',\n    '<template><MyComponent {{on \"change\" this.setContactUsSectionDescription}}/></template>',\n    '<template><MyComponent {{on \"change\" (fn this.setContactUsSectionDescription true)}}/></template>',\n    '<template><MyComponent {{on \"change\" (action \"setContactUsSectionDescription\")}}/></template>',\n    '<template><MyComponent {{on \"change\" (action \"setContactUsSectionDescription\" true)}}/></template>',\n    '<template><MyComponent {{action \"setIsDropdownOpen\" false}}/></template>',\n    '<template><MyComponent @dismissModal={{set this \"isRequestExpiredModalOpen\" false}}/></template>',\n    '<template><MyComponent onclick={{set this “expandVoluntarySelfIdHelpText” true}}/></template>',\n    '<template><MyComponent @click={{set this \"isCardCollapsed\" (if this.isCardCollapsed false true)}}/></template>',\n    '<template>{{my-component click=(set this \"isOpen\" false)}}</template>',\n    '<template>{{my-component click=(set this \"isLegalTextExpanded\" (not this.isLegalTextExpanded))}}</template>',\n    '<template>{{my-component onVisibilityChange=(set this “isDropdownOpen”)}}</template>',\n    '<template>{{my-component click=(set this “expandVoluntarySelfIdHelpText” true)}}</template>',\n    '<template>{{my-component value=this.secondaryProfileHeadline}}</template>',\n    '<template><div {{mutate this.isDropdownOpen}} class=\"muted mut\">Non-helper substrings with mut in them should not violate this rule.</div></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><Input @value={{this.name}} @onChange={{(mut this.name)}} /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not use the (mut) helper. Use regular setters or actions instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n    {\n      code: '<template>{{input value=(mut this.name)}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not use the (mut) helper. Use regular setters or actions instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n    {\n      code: '<template><CustomComponent @onChange={{(mut this.value)}} /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not use the (mut) helper. Use regular setters or actions instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n\n    {\n      code: '<template><MyComponent @toggled={{mut this.showAggregatedLine}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template>{{my-component value=(mut this.secondaryProfileHeadline)}}</template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template><MyComponent {{action (mut this.isDropdownOpen) false}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template><MyComponent @dismissModal={{action (mut this.isRequestExpiredModalOpen) false}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template><MyComponent @click={{action (mut this.isCardCollapsed) (if this.isCardCollapsed false true)}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template><MyComponent onclick={{fn (mut this.expandVoluntarySelfIdHelpText) true}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template><MyComponent @onVisibilityChange={{fn (mut this.isDropdownOpen)}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template>{{my-component click=(action (mut this.isOpen) false)}}</template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template>{{my-component click=(action (mut this.isLegalTextExpanded) (not this.isLegalTextExpanded))}}</template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template>{{my-component onVisibilityChange=(action (mut this.isDropdownOpen))}}</template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template>{{my-component click=(fn (mut this.showManageEventsModal) true)}}</template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: `<template><MyComponent\n          @onVisibilityChange={{action\n            (mut this.isDemographicsDropdownOpen)\n          }}\n        /></template>`,\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: `<template><MyComponent\n          @dismissModal={{action\n            (mut this.isNotificationsPostApprovalModalOpen)\n            false\n          }}\n        /></template>`,\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<template><MyComponent onchange={{action (mut this.contactUsSection.description) value=\"target.value\"}}/></template>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    // `mut` is a strict-mode ambient keyword — no import needed in .gjs strict\n    // mode templates.\n    {\n      code: '<template>{{mut foo bar}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not use the (mut) helper. Use regular setters or actions instead.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    // Config: setterAlternative should produce exactly one `{{...}}` wrapping\n    // around the user-supplied expression — even when the user accidentally\n    // included the braces themselves.\n    {\n      code: '<template><MyComponent onchange={{action (mut this.val) value=\"target.value\"}}/></template>',\n      output: null,\n      options: [{ setterAlternative: '(set this.foo)' }],\n      errors: [\n        {\n          message:\n            'Do not use the (mut) helper. Consider using a JS action or {{(set this.foo)}} instead.',\n        },\n      ],\n    },\n    {\n      code: '<template><MyComponent onchange={{action (mut this.val) value=\"target.value\"}}/></template>',\n      output: null,\n      options: [{ setterAlternative: '{{(set this.foo)}}' }],\n      errors: [\n        {\n          message:\n            'Do not use the (mut) helper. Consider using a JS action or {{(set this.foo)}} instead.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-mut-helper', rule, {\n  valid: [\n    '<MyComponent @toggled={{this.showAggregatedLine}}/>',\n    '<MyComponent @toggle={{set this \"isDropdownOpen\"}}/>',\n    '<MyComponent @onFocusOut={{action \"onFocusOutKeySkillsInput\" value=\"target.value\"}}/>',\n    '<MyComponent {{on \"click\" (set this \"isDropdownOpen\" false)}}/>',\n    '<MyComponent {{on \"change\" this.setContactUsSectionDescription}}/>',\n    '<MyComponent {{on \"change\" (fn this.setContactUsSectionDescription true)}}/>',\n    '<MyComponent {{on \"change\" (action \"setContactUsSectionDescription\")}}/>',\n    '<MyComponent {{on \"change\" (action \"setContactUsSectionDescription\" true)}}/>',\n    '<MyComponent {{action \"setIsDropdownOpen\" false}}/>',\n    '<MyComponent @dismissModal={{set this \"isRequestExpiredModalOpen\" false}}/>',\n    '<MyComponent onclick={{set this “expandVoluntarySelfIdHelpText” true}}/>',\n    '<MyComponent @click={{set this \"isCardCollapsed\" (if this.isCardCollapsed false true)}}/>',\n    '{{my-component click=(set this \"isOpen\" false)}}',\n    '{{my-component click=(set this \"isLegalTextExpanded\" (not this.isLegalTextExpanded))}}',\n    '{{my-component onVisibilityChange=(set this “isDropdownOpen”)}}',\n    '{{my-component click=(set this “expandVoluntarySelfIdHelpText” true)}}',\n    '{{my-component value=this.secondaryProfileHeadline}}',\n    '<div {{mutate this.isDropdownOpen}} class=\"muted mut\">Non-helper substrings with mut in them should not violate this rule.</div>',\n  ],\n  invalid: [\n    {\n      code: '<MyComponent @toggled={{mut this.showAggregatedLine}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '{{my-component value=(mut this.secondaryProfileHeadline)}}',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<MyComponent {{action (mut this.isDropdownOpen) false}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<MyComponent @dismissModal={{action (mut this.isRequestExpiredModalOpen) false}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<MyComponent @click={{action (mut this.isCardCollapsed) (if this.isCardCollapsed false true)}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<MyComponent onclick={{fn (mut this.expandVoluntarySelfIdHelpText) true}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<MyComponent @onVisibilityChange={{fn (mut this.isDropdownOpen)}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '{{my-component click=(action (mut this.isOpen) false)}}',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '{{my-component click=(action (mut this.isLegalTextExpanded) (not this.isLegalTextExpanded))}}',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '{{my-component onVisibilityChange=(action (mut this.isDropdownOpen))}}',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '{{my-component click=(fn (mut this.showManageEventsModal) true)}}',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: `<MyComponent\n          @onVisibilityChange={{action\n            (mut this.isDemographicsDropdownOpen)\n          }}\n        />`,\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: `<MyComponent\n          @dismissModal={{action\n            (mut this.isNotificationsPostApprovalModalOpen)\n            false\n          }}\n        />`,\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    {\n      code: '<MyComponent onchange={{action (mut this.contactUsSection.description) value=\"target.value\"}}/>',\n      output: null,\n      errors: [{ message: 'Do not use the (mut) helper. Use regular setters or actions instead.' }],\n    },\n    // Config: setterAlternative\n    {\n      code: '<MyComponent onchange={{action (mut this.val) value=\"target.value\"}}/>',\n      output: null,\n      options: [{ setterAlternative: 'mySetter' }],\n      errors: [\n        {\n          message:\n            'Do not use the (mut) helper. Consider using a JS action or {{mySetter}} instead.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-negated-condition.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-negated-condition');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-negated-condition', rule, {\n  valid: [\n    '<template>{{#if isValid}}Yes{{/if}}</template>',\n    '<template>{{#unless isInvalid}}Yes{{/unless}}</template>',\n    '<template>{{#if (eq value 1)}}Yes{{/if}}</template>',\n\n    '<template>{{#if condition}}<img>{{/if}}</template>',\n    '<template>{{#if (or c1 c2)}}{{/if}}</template>',\n    '<template>{{#if (not (or c1 c2))}}{{/if}}</template>',\n    '<template>{{#if (not c1 c2)}}{{/if}}</template>',\n    '<template>{{#if (not (not c1) c2)}}<img>{{/if}}</template>',\n    '<template>{{#if (not c1 (not c2))}}<img>{{/if}}</template>',\n\n    // simplifyHelpers: false config\n    {\n      code: '<template>{{#if (not (not c2))}}<img>{{/if}}</template>',\n      options: [{ simplifyHelpers: false }],\n    },\n    {\n      code: '<template>{{#if (not (eq c2))}}<img>{{/if}}</template>',\n      options: [{ simplifyHelpers: false }],\n    },\n\n    // if...else / if...else if patterns\n    '<template>{{#if condition}}<img>{{else}}<img>{{/if}}</template>',\n    '<template>{{#if (or c1 c2)}}<img>{{else}}<img>{{/if}}</template>',\n    '<template>{{#if condition}}<img>{{else if condition}}<img>{{/if}}</template>',\n    '<template>{{#if condition}}<img>{{else if (not condition2)}}<img>{{/if}}</template>',\n    '<template>{{#if (not condition)}}<img>{{else if (not condition2)}}<img>{{/if}}</template>',\n    '<template>{{#if condition}}<img>{{else if condition}}<img>{{else}}<img>{{/if}}</template>',\n    '<template>{{#if (not condition)}}<img>{{else if (not condition2)}}<img>{{else}}<img>{{/if}}</template>',\n\n    // unless variants\n    '<template>{{#unless (or c1 c2)}}<img>{{/unless}}</template>',\n    '<template>{{#unless condition}}<img>{{else}}<img>{{/unless}}</template>',\n    '<template>{{#unless condition}}<img>{{else if condition}}<img>{{/unless}}</template>',\n    '<template>{{#unless condition}}<img>{{else if condition}}<img>{{else}}<img>{{/unless}}</template>',\n\n    // MustacheStatement context (inline)\n    '<template><img class={{if condition \"some-class\"}}></template>',\n    '<template><img class={{if condition \"some-class\" \"other-class\"}}></template>',\n    '<template><img class={{unless condition \"some-class\"}}></template>',\n    '<template><img class={{if (not (or c1 c2)) \"some-class\"}}></template>',\n\n    // SubExpression context\n    '<template>{{input class=(if condition \"some-class\")}}</template>',\n    '<template>{{input class=(if condition \"some-class\" \"other-class\")}}</template>',\n    '<template>{{input class=(unless condition \"some-class\")}}</template>',\n    '<template>{{input class=(if (not (or c1 c2)) \"some-class\")}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{#if (not isValid)}}No{{/if}}</template>',\n      output: '<template>{{#unless isValid}}No{{/unless}}</template>',\n      errors: [{ messageId: 'useUnless' }],\n    },\n\n    {\n      code: '<template>{{#if (not condition)}}<img>{{/if}}</template>',\n      output: '<template>{{#unless condition}}<img>{{/unless}}</template>',\n      errors: [{ messageId: 'useUnless' }],\n    },\n    {\n      code: '<template>{{#if (not (not c1 c2))}}<img>{{/if}}</template>',\n      output: '<template>{{#if (or c1 c2)}}<img>{{/if}}</template>',\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      code: '<template>{{#if (not condition)}}<img>{{else}}<input>{{/if}}</template>',\n      output: '<template>{{#if condition}}<input>{{else}}<img>{{/if}}</template>',\n      errors: [{ messageId: 'flipIf' }],\n    },\n    {\n      code: '<template>{{#unless (not condition)}}<img>{{/unless}}</template>',\n      output: '<template>{{#if condition}}<img>{{/if}}</template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template>{{#unless (not (not condition))}}<img>{{/unless}}</template>',\n      output: '<template>{{#unless condition}}<img>{{/unless}}</template>',\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      code: '<template>{{#unless (not condition)}}<img>{{else}}<input>{{/unless}}</template>',\n      output: '<template>{{#if condition}}<img>{{else}}<input>{{/if}}</template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template>{{#unless (not condition)}}<img>{{else if (not condition)}}<input>{{/unless}}</template>',\n      output:\n        '<template>{{#if condition}}<img>{{else if (not condition)}}<input>{{/if}}</template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template>{{#unless (not condition)}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/unless}}</template>',\n      output:\n        '<template>{{#if condition}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/if}}</template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template>{{#if condition}}{{else}}{{! some comment }}{{#if (not condition)}}<img>{{/if}}{{/if}}</template>',\n      output:\n        '<template>{{#if condition}}{{else}}{{! some comment }}{{#unless condition}}<img>{{/unless}}{{/if}}</template>',\n      errors: [{ messageId: 'useUnless' }],\n    },\n    {\n      code: '<template>{{#if condition}}{{else}}{{#if (not condition)}}<img>{{/if}}{{/if}}</template>',\n      output:\n        '<template>{{#if condition}}{{else}}{{#unless condition}}<img>{{/unless}}{{/if}}</template>',\n      errors: [{ messageId: 'useUnless' }],\n    },\n    {\n      code: '<template><img class={{if (not condition) \"some-class\"}}></template>',\n      output: '<template><img class={{unless condition \"some-class\"}}></template>',\n      errors: [{ messageId: 'useUnless' }],\n    },\n    {\n      code: '<template><img class={{if (not condition) \"some-class\" \"other-class\"}}></template>',\n      output: '<template><img class={{if condition \"other-class\" \"some-class\"}}></template>',\n      errors: [{ messageId: 'flipIf' }],\n    },\n    {\n      code: '<template><img class={{unless (not condition) \"some-class\"}}></template>',\n      output: '<template><img class={{if condition \"some-class\"}}></template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template><img class={{unless (not condition) \"some-class\" \"other-class\"}}></template>',\n      output: '<template><img class={{if condition \"some-class\" \"other-class\"}}></template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template><img class={{unless (not (not condition)) \"some-class\" \"other-class\"}}></template>',\n      output: '<template><img class={{unless condition \"some-class\" \"other-class\"}}></template>',\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      code: '<template>{{input class=(if (not condition) \"some-class\")}}</template>',\n      output: '<template>{{input class=(unless condition \"some-class\")}}</template>',\n      errors: [{ messageId: 'useUnless' }],\n    },\n    {\n      code: '<template>{{input class=(if (not condition) \"some-class\" \"other-class\")}}</template>',\n      output: '<template>{{input class=(if condition \"other-class\" \"some-class\")}}</template>',\n      errors: [{ messageId: 'flipIf' }],\n    },\n    {\n      code: '<template>{{input class=(unless (not condition) \"some-class\")}}</template>',\n      output: '<template>{{input class=(if condition \"some-class\")}}</template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template>{{input class=(unless (not condition) \"some-class\" \"other-class\")}}</template>',\n      output: '<template>{{input class=(if condition \"some-class\" \"other-class\")}}</template>',\n      errors: [{ messageId: 'useIf' }],\n    },\n    {\n      code: '<template>{{input class=(unless (not (not condition)) \"some-class\" \"other-class\")}}</template>',\n      output: '<template>{{input class=(unless condition \"some-class\" \"other-class\")}}</template>',\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-negated-condition', rule, {\n  valid: [\n    '{{#if condition}}<img>{{/if}}',\n    '{{#if (or c1 c2)}}{{/if}}',\n    '{{#if (not (or c1 c2))}}{{/if}}',\n    '{{#if (not c1 c2)}}{{/if}}',\n    '{{#if (not (not c1) c2)}}<img>{{/if}}',\n    '{{#if (not c1 (not c2))}}<img>{{/if}}',\n    '{{#if condition}}<img>{{else}}<img>{{/if}}',\n    '{{#if (or c1 c2)}}<img>{{else}}<img>{{/if}}',\n    '{{#if condition}}<img>{{else if condition}}<img>{{/if}}',\n    '{{#if condition}}<img>{{else if (not condition2)}}<img>{{/if}}',\n    '{{#if (not condition)}}<img>{{else if (not condition2)}}<img>{{/if}}',\n    '{{#if condition}}<img>{{else if condition}}<img>{{else}}<img>{{/if}}',\n    '{{#if (not condition)}}<img>{{else if (not condition2)}}<img>{{else}}<img>{{/if}}',\n    '{{#unless condition}}<img>{{/unless}}',\n    '{{#unless (or c1 c2)}}<img>{{/unless}}',\n    '{{#unless (not c1 c2)}}<img>{{/unless}}',\n    '{{#unless condition}}<img>{{else}}<img>{{/unless}}',\n    '{{#unless (or c1 c2)}}<img>{{else}}<img>{{/unless}}',\n    '{{#unless condition}}<img>{{else if condition}}<img>{{/unless}}',\n    '{{#unless (or c1 c2)}}<img>{{else if (or c1 c2)}}<img>{{/unless}}',\n    '{{#unless condition}}<img>{{else if condition}}<img>{{else}}<img>{{/unless}}',\n    '{{#unless (or c1 c2)}}<img>{{else if (or c1 c2)}}<img>{{else}}<img>{{/unless}}',\n    '<img class={{if condition \"some-class\"}}>',\n    '<img class={{if (or c1 c2) \"some-class\"}}>',\n    '<img class={{if (not (or c1 c2)) \"some-class\"}}>',\n    '<img class={{if (not c1 c2) \"some-class\"}}>',\n    '<img class={{if condition \"some-class\" \"other-class\"}}>',\n    '<img class={{if (or c1 c2) \"some-class\" \"other-class\"}}>',\n    '<img class={{unless condition \"some-class\"}}>',\n    '<img class={{unless (or c1 c2) \"some-class\"}}>',\n    '<img class={{unless (not c1 c2) \"some-class\"}}>',\n    '<img class={{unless condition \"some-class\" \"other-class\"}}>',\n    '<img class={{unless (or c1 c2) \"some-class\" \"other-class\"}}>',\n    '{{input class=(if condition \"some-class\")}}',\n    '{{input class=(if (or c1 c2) \"some-class\")}}',\n    '{{input class=(if (not (or c1 c2)) \"some-class\")}}',\n    '{{input class=(if (not c1 c2) \"some-class\")}}',\n    '{{input class=(if condition \"some-class\" \"other-class\")}}',\n    '{{input class=(if (or c1 c2) \"some-class\" \"other-class\")}}',\n    '{{input class=(unless condition \"some-class\")}}',\n    '{{input class=(unless (or c1 c2) \"some-class\")}}',\n    '{{input class=(unless condition \"some-class\" \"other-class\")}}',\n    '{{input class=(unless (or c1 c2) \"some-class\" \"other-class\")}}',\n    // simplifyHelpers: false allows nested not/eq helpers without error.\n    {\n      code: '{{#if (not (not c2))}}<img>{{/if}}',\n      options: [{ simplifyHelpers: false }],\n    },\n    {\n      code: '{{#if (not (eq c2))}}<img>{{/if}}',\n      options: [{ simplifyHelpers: false }],\n    },\n    // simplifyHelpers: false — if with nested fixable helpers is valid.\n    {\n      code: '<img class={{if (not (gte c 10)) \"some-class\"}}>',\n      options: [{ simplifyHelpers: false }],\n    },\n    {\n      code: '{{input class=(if (not (lte c 10)) \"some-class\" \"other-class\")}}',\n      options: [{ simplifyHelpers: false }],\n    },\n  ],\n  invalid: [\n    {\n      code: '{{#if (not condition)}}<img>{{/if}}',\n      output: '{{#unless condition}}<img>{{/unless}}',\n      errors: [{ message: 'Change `if (not condition)` to `unless condition`.' }],\n    },\n    {\n      code: '{{#if (not (not condition))}}<img>{{/if}}',\n      output: '{{#if condition}}<img>{{/if}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{#if (not (not c1 c2))}}<img>{{/if}}',\n      output: '{{#if (or c1 c2)}}<img>{{/if}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{#if (not (eq c1 c2))}}<img>{{/if}}',\n      output: '{{#if (not-eq c1 c2)}}<img>{{/if}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{#if (not condition)}}<img>{{else}}<input>{{/if}}',\n      output: '{{#if condition}}<input>{{else}}<img>{{/if}}',\n      errors: [\n        {\n          message:\n            'Change `{{if (not condition)}} ... {{else}} ... {{/if}}` to `{{if condition}} ... {{else}} ... {{/if}}`.',\n        },\n      ],\n    },\n    {\n      code: '{{#unless (not condition)}}<img>{{/unless}}',\n      output: '{{#if condition}}<img>{{/if}}',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '{{#unless (not (not condition))}}<img>{{/unless}}',\n      output: '{{#unless condition}}<img>{{/unless}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{#unless (not condition)}}<img>{{else}}<input>{{/unless}}',\n      output: '{{#if condition}}<img>{{else}}<input>{{/if}}',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '{{#unless (not (not-eq c1 c2))}}<img>{{else}}<input>{{/unless}}',\n      output: '{{#unless (eq c1 c2)}}<img>{{else}}<input>{{/unless}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{#unless (not condition)}}<img>{{else if (not condition)}}<input>{{/unless}}',\n      output: '{{#if condition}}<img>{{else if (not condition)}}<input>{{/if}}',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '{{#unless (not (not condition))}}<img>{{else if (not (not condition))}}<input>{{/unless}}',\n      output: '{{#unless condition}}<img>{{else if (not (not condition))}}<input>{{/unless}}',\n      errors: [\n        { message: 'Simplify unnecessary negation of helper.' },\n        { message: 'Simplify unnecessary negation of helper.' },\n      ],\n    },\n    {\n      code: '{{#unless (not (gt c 10))}}<img>{{else if (not (lt c 5))}}<input>{{/unless}}',\n      output: '{{#unless (lte c 10)}}<img>{{else if (not (lt c 5))}}<input>{{/unless}}',\n      errors: [\n        { message: 'Simplify unnecessary negation of helper.' },\n        { message: 'Simplify unnecessary negation of helper.' },\n      ],\n    },\n    {\n      code: '{{#unless (not condition)}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/unless}}',\n      output: '{{#if condition}}<img>{{else if (not condition)}}<input>{{else}}<hr>{{/if}}',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '{{#unless (not condition)}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/unless}}',\n      output: '{{#if condition}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/if}}',\n      errors: [\n        { message: 'Change `unless (not condition)` to `if condition`.' },\n        { message: 'Simplify unnecessary negation of helper.' },\n      ],\n    },\n    {\n      code: '{{#if condition}}{{else}}{{! some comment }}{{#if (not condition)}}<img>{{/if}}{{/if}}',\n      output:\n        '{{#if condition}}{{else}}{{! some comment }}{{#unless condition}}<img>{{/unless}}{{/if}}',\n      errors: [{ message: 'Change `if (not condition)` to `unless condition`.' }],\n    },\n    {\n      code: '{{#if condition}}{{else}}{{#if (not condition)}}<img>{{/if}}{{/if}}',\n      output: '{{#if condition}}{{else}}{{#unless condition}}<img>{{/unless}}{{/if}}',\n      errors: [{ message: 'Change `if (not condition)` to `unless condition`.' }],\n    },\n    {\n      code: '<img class={{if (not condition) \"some-class\"}}>',\n      output: '<img class={{unless condition \"some-class\"}}>',\n      errors: [{ message: 'Change `if (not condition)` to `unless condition`.' }],\n    },\n    {\n      code: '<img class={{if (not (gte c 10)) \"some-class\"}}>',\n      output: '<img class={{if (lt c 10) \"some-class\"}}>',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '<img class={{if (not condition) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{if condition \"other-class\" \"some-class\"}}>',\n      errors: [\n        {\n          message:\n            'Change `{{if (not condition)}} ... {{else}} ... {{/if}}` to `{{if condition}} ... {{else}} ... {{/if}}`.',\n        },\n      ],\n    },\n    {\n      code: '<img class={{if (not (not condition)) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{if condition \"some-class\" \"other-class\"}}>',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '<img class={{unless (not condition) \"some-class\"}}>',\n      output: '<img class={{if condition \"some-class\"}}>',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '<img class={{unless (not condition) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{if condition \"some-class\" \"other-class\"}}>',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '<img class={{unless (not (not condition)) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{unless condition \"some-class\" \"other-class\"}}>',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{input class=(if (not condition) \"some-class\")}}',\n      output: '{{input class=(unless condition \"some-class\")}}',\n      errors: [{ message: 'Change `if (not condition)` to `unless condition`.' }],\n    },\n    {\n      code: '{{input class=(if (not condition) \"some-class\" \"other-class\")}}',\n      output: '{{input class=(if condition \"other-class\" \"some-class\")}}',\n      errors: [\n        {\n          message:\n            'Change `{{if (not condition)}} ... {{else}} ... {{/if}}` to `{{if condition}} ... {{else}} ... {{/if}}`.',\n        },\n      ],\n    },\n    {\n      code: '{{input class=(if (not (lte c 10)) \"some-class\" \"other-class\")}}',\n      output: '{{input class=(if (gt c 10) \"some-class\" \"other-class\")}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n    {\n      code: '{{input class=(unless (not condition) \"some-class\")}}',\n      output: '{{input class=(if condition \"some-class\")}}',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '{{input class=(unless (not condition) \"some-class\" \"other-class\")}}',\n      output: '{{input class=(if condition \"some-class\" \"other-class\")}}',\n      errors: [{ message: 'Change `unless (not condition)` to `if condition`.' }],\n    },\n    {\n      code: '{{input class=(unless (not (not condition)) \"some-class\" \"other-class\")}}',\n      output: '{{input class=(unless condition \"some-class\" \"other-class\")}}',\n      errors: [{ message: 'Simplify unnecessary negation of helper.' }],\n    },\n\n    // ******************************************\n    // simplifyHelpers: true (explicit) — BlockStatement\n    // ******************************************\n    {\n      // if (not (not ...)) with simplifyHelpers: true\n      code: '{{#if (not (not condition))}}<img>{{/if}}',\n      output: '{{#if condition}}<img>{{/if}}',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      // if (not (eq ...)) with simplifyHelpers: true\n      code: '{{#if (not (eq c1 c2))}}<img>{{/if}}',\n      output: '{{#if (not-eq c1 c2)}}<img>{{/if}}',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      // unless (not (not-eq ...)) else with simplifyHelpers: true\n      code: '{{#unless (not (not-eq c1 c2))}}<img>{{else}}<input>{{/unless}}',\n      output: '{{#unless (eq c1 c2)}}<img>{{else}}<input>{{/unless}}',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      // unless ... else if with fixable helpers — two errors with simplifyHelpers: true\n      code: '{{#unless (not (gt c 10))}}<img>{{else if (not (lt c 5))}}<input>{{/unless}}',\n      output: '{{#unless (lte c 10)}}<img>{{else if (not (lt c 5))}}<input>{{/unless}}',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }, { messageId: 'negatedHelper' }],\n    },\n    {\n      // unless ... else if ... else — two errors with simplifyHelpers: true\n      code: '{{#unless (not condition)}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/unless}}',\n      output: '{{#if condition}}<img>{{else if (not (not c1 c2))}}<input>{{else}}<hr>{{/if}}',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'useIf' }, { messageId: 'negatedHelper' }],\n    },\n\n    // ******************************************\n    // simplifyHelpers: true (explicit) — MustacheStatement\n    // ******************************************\n    {\n      // if (not (gte ...)) with simplifyHelpers: true\n      code: '<img class={{if (not (gte c 10)) \"some-class\"}}>',\n      output: '<img class={{if (lt c 10) \"some-class\"}}>',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n    {\n      // if (not (not ...)) else with simplifyHelpers: true\n      code: '<img class={{if (not (not condition)) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{if condition \"some-class\" \"other-class\"}}>',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n\n    // ******************************************\n    // simplifyHelpers: true (explicit) — SubExpression\n    // ******************************************\n    {\n      // if (not (lte ...)) else with simplifyHelpers: true\n      code: '{{input class=(if (not (lte c 10)) \"some-class\" \"other-class\")}}',\n      output: '{{input class=(if (gt c 10) \"some-class\" \"other-class\")}}',\n      options: [{ simplifyHelpers: true }],\n      errors: [{ messageId: 'negatedHelper' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-nested-interactive.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-nested-interactive');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-nested-interactive', rule, {\n  valid: [\n    `<template>\n      <button>Click me</button>\n    </template>`,\n    `<template>\n      <a href=\"#test\">Link</a>\n    </template>`,\n    `<template>\n      <div>\n        <button>Button 1</button>\n        <button>Button 2</button>\n      </div>\n    </template>`,\n    `<template>\n      <div>\n        <input type=\"text\" />\n      </div>\n    </template>`,\n    `<template>\n      <label>\n        <input type=\"hidden\" />\n        Text\n      </label>\n    </template>`,\n    // Canonical ARIA composite-widget hierarchies — driven by aria-query's\n    // `requiredOwnedElements` (see lib/utils/interactive-roles.js). These are\n    // WAI-ARIA APG patterns and must not be flagged.\n    '<template><div role=\"listbox\"><div role=\"option\">A</div><div role=\"option\">B</div></div></template>',\n    '<template><div role=\"tablist\"><div role=\"tab\">Tab 1</div><div role=\"tab\">Tab 2</div></div></template>',\n    '<template><div role=\"tree\"><div role=\"treeitem\">Node</div></div></template>',\n    '<template><div role=\"grid\"><div role=\"row\"><div role=\"gridcell\">Cell</div></div></div></template>',\n    '<template><div role=\"grid\"><div role=\"row\"><div role=\"rowheader\">Header</div></div></div></template>',\n    '<template><div role=\"grid\"><div role=\"row\"><div role=\"columnheader\">Header</div></div></div></template>',\n    '<template><div role=\"treegrid\"><div role=\"row\"><div role=\"gridcell\">Cell</div></div></div></template>',\n    '<template><div role=\"radiogroup\"><div role=\"radio\">Opt 1</div></div></template>',\n\n    // <audio>/<video> without `controls` are NOT interactive (no rendered UI, no focus).\n    '<template><button><audio></audio></button></template>',\n    '<template><button><video></video></button></template>',\n    `<template>\n      <div role=\"presentation\">\n        <button>Click</button>\n      </div>\n    </template>`,\n\n    '<template><button>button</button></template>',\n    '<template><button>button <strong>!!!</strong></button></template>',\n    '<template><a><button>button</button></a></template>',\n    '<template><a href=\"/\">link</a></template>',\n    '<template><a href=\"/\">link <strong>!!!</strong></a></template>',\n    '<template><button><input type=\"hidden\"></button></template>',\n    '<template><div tabindex=-1><button>Click me!</button></div></template>',\n    '<template><div tabindex=\"1\"><button></button></div></template>',\n    '<template><label><input></label></template>',\n    // Config: ignoreUsemapAttribute (alias for ignoreUsemap)\n    {\n      code: '<template><button><img usemap=\"\"></button></template>',\n      options: [{ ignoreUsemapAttribute: true }],\n    },\n    '<template><details><summary>Details</summary>Something small enough to escape casual notice.</details></template>',\n    '<template><details> <summary>Details</summary>Something small enough to escape casual notice.</details></template>',\n    '<template><details><summary>Advanced</summary><label for=\"x\">Field</label><input id=\"x\" /></details></template>',\n    '<template><details><summary>More</summary><div><button type=\"button\">Action</button></div></details></template>',\n    `<template>\n    <ul role=\"menubar\" aria-label=\"functions\" id=\"appmenu\">\n      <li role=\"menuitem\" aria-haspopup=\"true\">\n        File\n        <ul role=\"menu\">\n          <li role=\"menuitem\">New</li>\n          <li role=\"menuitem\">Open</li>\n          <li role=\"menuitem\">Print</li>\n        </ul>\n      </li>\n    </ul>\n    </template>`,\n\n    // Mixed menu-item variants — `menuitemcheckbox` and `menuitemradio`\n    // nested alongside plain `menuitem` follow the same APG Menu Button\n    // pattern. Should not flag as nested-interactive.\n    `<template>\n    <ul role=\"menu\" aria-label=\"options\">\n      <li role=\"menuitemcheckbox\" aria-checked=\"false\">Show hidden</li>\n      <li role=\"menuitemradio\" aria-checked=\"true\">Sort by name</li>\n      <li role=\"menuitemradio\" aria-checked=\"false\">Sort by date</li>\n    </ul>\n    </template>`,\n    // Submenu attached to a menuitemcheckbox (APG permits this).\n    `<template>\n    <ul role=\"menu\">\n      <li role=\"menuitemcheckbox\" aria-haspopup=\"true\" aria-checked=\"false\">\n        Advanced\n        <ul role=\"menu\">\n          <li role=\"menuitem\">Inspect</li>\n          <li role=\"menuitem\">Export</li>\n        </ul>\n      </li>\n    </ul>\n    </template>`,\n    `<template>\n  <label> My input:\n    {{#if @select}}\n      <select></select>\n    {{else}}\n      <input type='text'>\n    {{/if}}\n  </label>\n    </template>`,\n    `<template>\n  <label> My input:\n    {{#if @select}}\n      {{#if @multiple}}\n        <select multiple></select>\n      {{else}}\n        <select></select>\n      {{/if}}\n    {{else}}\n      <input type='text'>\n    {{/if}}\n  </label>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <button>\n          <a href=\"#\">Link</a>\n        </button>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: `<template>\n        <a href=\"#\">\n          <button>Click</button>\n        </a>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: `<template>\n        <button>\n          <input type=\"text\" />\n        </button>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: `<template>\n        <div role=\"button\">\n          <a href=\"#\">Link</a>\n        </div>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n\n    {\n      code: '<template><details>Something small enough to escape casual notice.<summary>Details</summary></details></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><summary><a href=\"/\">button</a></summary></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><a href=\"/\">button<a href=\"/\">!</a></a></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><a href=\"/\">button<button>!</button></a></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button>button<a href=\"/\">!</a></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button>button<button>!</button></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><input type=\"text\"></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><details><p>!</p></details></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><embed type=\"video/quicktime\" src=\"movie.mov\" width=\"640\" height=\"480\"></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><iframe src=\"/frame.html\" width=\"640\" height=\"480\"></iframe></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><select></select></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><textarea></textarea></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><div tabindex=\"1\"></div></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><img usemap=\"\"></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    // Label-association: HTML's \"first labelable descendant\" rule means a\n    // second interactive child is orphaned from the label. Flag per upstream\n    // ember-template-lint parity.\n    {\n      code: `<template>\n        <label>\n          <button>Click</button>\n          <a href=\"#\">Link</a>\n        </label>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><label><input><input></label></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    // <object usemap> is interactive via rule-level special case (upstream parity).\n    {\n      code: '<template><object usemap=\"\"><button></button></object></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    // <audio controls> / <video controls> are HTML interactive content; nesting inside <button> fires.\n    {\n      code: '<template><button><video controls></video></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    {\n      code: '<template><button><audio controls></audio></button></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    // <canvas> is interactive (drawing/game-UI convention); nesting fires even with tabindex.\n    {\n      code: '<template><canvas tabindex=\"0\"><button>Click</button></canvas></template>',\n      output: null,\n      errors: [{ messageId: 'nested' }],\n    },\n    // Error message surfaces the attribute that makes a <div> parent interactive\n    // (regression test for the original report against `<div role=\"menu\">`).\n    {\n      code: '<template><div role=\"menu\"><input type=\"search\"></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not nest interactive element <input> inside <div role=\"menu\">.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-nested-interactive', rule, {\n  valid: [\n    '<button>button</button>',\n    '<button>button <strong>!!!</strong></button>',\n    '<a><button>button</button></a>',\n    '<a href=\"/\">link</a>',\n    '<a href=\"/\">link <strong>!!!</strong></a>',\n    '<button><input type=\"hidden\"></button>',\n    '<div tabindex=-1><button>Click me!</button></div>',\n    '<div tabindex=\"1\"><button></button></div>',\n    '<label><input></label>',\n    '<details><summary>Details</summary>Something small enough to escape casual notice.</details>',\n    '<details> <summary>Details</summary>Something small enough to escape casual notice.</details>',\n    '<details><summary>Advanced</summary><label for=\"x\">Field</label><input id=\"x\" /></details>',\n    '<details><summary>More</summary><div><button type=\"button\">Action</button></div></details>',\n    `\n    <ul role=\"menubar\" aria-label=\"functions\" id=\"appmenu\">\n      <li role=\"menuitem\" aria-haspopup=\"true\">\n        File\n        <ul role=\"menu\">\n          <li role=\"menuitem\">New</li>\n          <li role=\"menuitem\">Open</li>\n          <li role=\"menuitem\">Print</li>\n        </ul>\n      </li>\n    </ul>\n    `,\n    `\n  <label> My input:\n    {{#if @select}}\n      <select></select>\n    {{else}}\n      <input type='text'>\n    {{/if}}\n  </label>\n    `,\n    `\n  <label> My input:\n    {{#if @select}}\n      {{#if @multiple}}\n        <select multiple></select>\n      {{else}}\n        <select></select>\n      {{/if}}\n    {{else}}\n      <input type='text'>\n    {{/if}}\n  </label>\n    `,\n    // Config: ignoredTags\n    {\n      code: '<button><input></button>',\n      options: [{ ignoredTags: ['button'] }],\n    },\n    // Config: ignoreTabindex\n    {\n      code: '<button><div tabindex=-1></div></button>',\n      options: [{ ignoreTabindex: true }],\n    },\n    // Config: ignoreUsemap\n    {\n      code: '<button><img usemap=\"\"></button>',\n      options: [{ ignoreUsemap: true }],\n    },\n    // Config: ignoreUsemapAttribute (alias for ignoreUsemap)\n    {\n      code: '<button><img usemap=\"\"></button>',\n      options: [{ ignoreUsemapAttribute: true }],\n    },\n    // <audio>/<video> without `controls` are NOT interactive.\n    '<button><audio></audio></button>',\n    '<button><video></video></button>',\n\n    // Canonical ARIA composite-widget hierarchies.\n    '<div role=\"listbox\"><div role=\"option\">A</div><div role=\"option\">B</div></div>',\n    '<div role=\"tablist\"><div role=\"tab\">Tab 1</div><div role=\"tab\">Tab 2</div></div>',\n    '<div role=\"tree\"><div role=\"treeitem\">Node</div></div>',\n    '<div role=\"grid\"><div role=\"row\"><div role=\"gridcell\">Cell</div></div></div>',\n    '<div role=\"grid\"><div role=\"row\"><div role=\"rowheader\">Header</div></div></div>',\n    '<div role=\"grid\"><div role=\"row\"><div role=\"columnheader\">Header</div></div></div>',\n    '<div role=\"treegrid\"><div role=\"row\"><div role=\"gridcell\">Cell</div></div></div>',\n    '<div role=\"radiogroup\"><div role=\"radio\">Opt 1</div></div>',\n  ],\n  invalid: [\n    {\n      code: '<details>Something small enough to escape casual notice.<summary>Details</summary></details>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <summary> inside <details>.' }],\n    },\n    {\n      code: '<summary><a href=\"/\">button</a></summary>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <a> inside <summary>.' }],\n    },\n    {\n      code: '<a href=\"/\">button<a href=\"/\">!</a></a>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <a> inside <a>.' }],\n    },\n    {\n      code: '<a href=\"/\">button<button>!</button></a>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <button> inside <a>.' }],\n    },\n    {\n      code: '<button>button<a href=\"/\">!</a></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <a> inside <button>.' }],\n    },\n    {\n      code: '<button>button<button>!</button></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <button> inside <button>.' }],\n    },\n    {\n      code: '<button><input type=\"text\"></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <input> inside <button>.' }],\n    },\n    {\n      code: '<button><details><p>!</p></details></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <details> inside <button>.' }],\n    },\n    {\n      code: '<button><embed type=\"video/quicktime\" src=\"movie.mov\" width=\"640\" height=\"480\"></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <embed> inside <button>.' }],\n    },\n    {\n      code: '<button><iframe src=\"/frame.html\" width=\"640\" height=\"480\"></iframe></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <iframe> inside <button>.' }],\n    },\n    {\n      code: '<button><select></select></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <select> inside <button>.' }],\n    },\n    {\n      code: '<button><textarea></textarea></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <textarea> inside <button>.' }],\n    },\n    {\n      code: '<button><div tabindex=\"1\"></div></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <div tabindex=\"1\"> inside <button>.' }],\n    },\n    {\n      code: '<button><img usemap=\"\"></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <img> inside <button>.' }],\n    },\n    // <object usemap> is interactive via rule-level special case (upstream parity).\n    {\n      code: '<object usemap=\"\"><button></button></object>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <button> inside <object>.' }],\n    },\n    // Label-association: HTML's \"first labelable descendant\" rule means a\n    // second interactive child is orphaned from the label.\n    {\n      code: '<label><button>Click</button><a href=\"#\">Link</a></label>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <a> inside <label>.' }],\n    },\n    {\n      code: '<label><input><input></label>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <input> inside <label>.' }],\n    },\n    // Config: additionalInteractiveTags\n    {\n      code: '<button><my-special-input></my-special-input></button>',\n      output: null,\n      options: [{ additionalInteractiveTags: ['my-special-input'] }],\n      errors: [{ message: 'Do not nest interactive element <my-special-input> inside <button>.' }],\n    },\n    // <video controls> is HTML interactive content; nesting inside <button> fires.\n    {\n      code: '<button><video controls></video></button>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <video> inside <button>.' }],\n    },\n    // <canvas> is interactive (drawing/game-UI convention); nesting fires even with tabindex.\n    {\n      code: '<canvas tabindex=\"0\"><button>Click</button></canvas>',\n      output: null,\n      errors: [{ message: 'Do not nest interactive element <button> inside <canvas>.' }],\n    },\n    // Error message surfaces the attribute that makes a <div> parent interactive,\n    // so authors can see *why* the rule fired without inspecting the rule source.\n    {\n      code: '<div role=\"menu\"><input type=\"search\"></div>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not nest interactive element <input> inside <div role=\"menu\">.',\n        },\n      ],\n    },\n    {\n      code: '<div role=\"menu\"><button type=\"button\">Delete</button></div>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not nest interactive element <button> inside <div role=\"menu\">.',\n        },\n      ],\n    },\n    {\n      code: '<div contenteditable><button>Edit</button></div>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not nest interactive element <button> inside <div contenteditable>.',\n        },\n      ],\n    },\n    {\n      code: '<div contenteditable=\"plaintext-only\"><button>Edit</button></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not nest interactive element <button> inside <div contenteditable=\"plaintext-only\">.',\n        },\n      ],\n    },\n    // Child-side enrichment: a <div role=\"button\"> child surfaces its role too.\n    {\n      code: '<button><div role=\"button\">Inner</div></button>',\n      output: null,\n      errors: [\n        {\n          message: 'Do not nest interactive element <div role=\"button\"> inside <button>.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-nested-landmark.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-nested-landmark');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-nested-landmark', rule, {\n  valid: [\n    `<template>\n      <nav>Navigation</nav>\n      <main>Content</main>\n    </template>`,\n    `<template>\n      <div>\n        <nav>Nav 1</nav>\n        <nav>Nav 2</nav>\n      </div>\n    </template>`,\n    `<template>\n      <main>\n        <div>Content</div>\n      </main>\n    </template>`,\n    `<template>\n      <div role=\"navigation\">Nav</div>\n      <div role=\"main\">Content</div>\n    </template>`,\n\n    '<template><div><main></main></div></template>',\n    '<template><div role=\"application\"><div role=\"document\"><div role=\"application\"></div></div></div></template>',\n    '<template><header><nav></nav></header></template>',\n    '<template><div role=\"banner\"><nav></nav></div></template>',\n    '<template><header><div role=\"navigation\"></div></header></template>',\n    '<template><div role=\"banner\"><div role=\"navigation\"></div></div></template>',\n\n    // `<section>` only gets the `region` landmark role when it has an accessible name\n    // (aria-label/aria-labelledby/title). Without one it has the generic role — see\n    // https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/HTML5.html\n    // This rule does not inspect accessible names, so unnamed sections are excluded.\n    '<template><section><section>Content</section></section></template>',\n    // `role=\"region\"` is the landmark role a named `<section>` gets. Nesting it is\n    // excluded for the same reason: the rule cannot verify an accessible name is present.\n    '<template><div role=\"region\"><div role=\"region\">Content</div></div></template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <nav>\n          <nav>Content</nav>\n        </nav>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Landmark elements should not be nested within other landmarks.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <main>\n          <main>Content</main>\n        </main>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Landmark elements should not be nested within other landmarks.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div role=\"main\">\n          <div role=\"main\">Nav</div>\n        </div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Landmark elements should not be nested within other landmarks.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><main><main></main></main></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><main><div><main></main></div></main></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><div role=\"main\"><main></main></div></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><div role=\"main\"><div><main></main></div></div></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><main><div role=\"main\"></div></main></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><main><div><div role=\"main\"></div></div></main></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><nav><nav></nav></nav></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><header><header></header></header></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><header><div role=\"banner\"></div></header></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<template><div role=\"contentinfo\"><footer></footer></div></template>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-nested-landmark', rule, {\n  valid: [\n    '<div><main></main></div>',\n    '<div role=\"application\"><div role=\"document\"><div role=\"application\"></div></div></div>',\n    '<header><nav></nav></header>',\n    '<div role=\"banner\"><nav></nav></div>',\n    '<header><div role=\"navigation\"></div></header>',\n    '<div role=\"banner\"><div role=\"navigation\"></div></div>',\n  ],\n  invalid: [\n    {\n      code: '<main><main></main></main>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<main><div><main></main></div></main>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<div role=\"main\"><main></main></div>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<div role=\"main\"><div><main></main></div></div>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<main><div role=\"main\"></div></main>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<main><div><div role=\"main\"></div></div></main>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<nav><nav></nav></nav>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<header><header></header></header>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<header><div role=\"banner\"></div></header>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n    {\n      code: '<div role=\"contentinfo\"><footer></footer></div>',\n      output: null,\n      errors: [{ message: 'Landmark elements should not be nested within other landmarks.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-nested-splattributes.js",
    "content": "const rule = require('../../../lib/rules/template-no-nested-splattributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-nested-splattributes', rule, {\n  valid: [\n    // Note: In standalone gjs/gts templates, the concept of \"top-level\" is different.\n    // These tests focus on the clear nested cases.\n    '<template><div><p>No splattributes here</p></div></template>',\n\n    '<template><div>...</div></template>',\n    '<template><div><div ...attributes>...</div></div></template>',\n    '<template><div ...attributes>...</div></template>',\n    '<template><div ...attributes>...</div><div ...attributes>...</div></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><div ...attributes><span ...attributes>Text</span></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: '<template><section ...attributes><div><button ...attributes>Click</button></div></section></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: '<template><div ...attributes class=\"wrapper\"><input ...attributes /></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n\n    {\n      code: `<template><div ...attributes>\n  <div ...attributes>\n    ...\n  </div>\n</div>\n</template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n        },\n      ],\n    },\n    {\n      code: `<template><div ...attributes>\n  <div>\n    <div ...attributes>\n    ...\n    </div>\n  </div>\n</div>\n</template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-nested-splattributes', rule, {\n  valid: [\n    '<div>...</div>',\n    '<div><div ...attributes>...</div></div>',\n    '<div ...attributes>...</div>',\n    '<div ...attributes>...</div><div ...attributes>...</div>',\n  ],\n  invalid: [\n    {\n      code: '<div ...attributes>\\n  <div ...attributes>\\n    ...\\n  </div>\\n</div>\\n',\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n        },\n      ],\n    },\n    {\n      code: '<div ...attributes>\\n  <div>\\n    <div ...attributes>\\n    ...\\n    </div>\\n  </div>\\n</div>\\n',\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use ...attributes on nested elements. Only use it on the top-level element of a component.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-obscure-array-access.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-obscure-array-access');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\n// Note: @each and [] are not valid in standard Glimmer templates and cause parse errors\n// This rule is designed to catch edge cases or custom syntax if they were to exist\n// We'll use simpler valid/invalid cases that actually parse\nruleTester.run('template-no-obscure-array-access', rule, {\n  valid: [\n    '<template>{{items}}</template>',\n    '<template>{{this.items}}</template>',\n    '<template>{{#each items as |item|}}{{item.name}}{{/each}}</template>',\n    '<template>{{get items 0}}</template>',\n    '<template>{{items.firstObject.name}}</template>',\n\n    \"<template>{{foo bar=(get this 'list.0' )}}</template>\",\n    \"<template><Foo @bar={{get this 'list.0'}} /></template>\",\n    \"<template>{{get this 'list.0'}}</template>\",\n    '<template>{{foo bar @list}}</template>',\n    '<template>Just a regular text in the template bar.[1] bar.1</template>',\n    '<template><Foo foo=\"bar.[1]\" /></template>',\n    `<template><FooBar\n    @subHeaderText={{if\n      this.isFooBarV2Enabled\n      \"foobar\"\n    }}\n  /></template>`,\n  ],\n  invalid: [\n    // Since @each and [] cause parse errors, this rule serves as documentation\n    // In practice, the parser will catch these issues before the rule runs\n\n    {\n      code: '<template><Foo @onClick={{fn this.func @foo.0.bar}} /></template>',\n      output: '<template><Foo @onClick={{fn this.func (get @foo \"0.bar\")}} /></template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    {\n      code: '<template>{{foo bar=this.list.[0]}}</template>',\n      output: '<template>{{foo bar=(get this.list \"0\")}}</template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    {\n      code: '<template>{{foo bar=@list.[1]}}</template>',\n      output: '<template>{{foo bar=(get @list \"1\")}}</template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    {\n      code: '<template>{{this.list.[0]}}</template>',\n      output: '<template>{{get this.list \"0\"}}</template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    {\n      code: '<template>{{this.list.[0].name}}</template>',\n      output: '<template>{{get this.list \"0.name\"}}</template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    {\n      code: '<template><Foo @bar={{this.list.[0]}} /></template>',\n      output: '<template><Foo @bar={{get this.list \"0\"}} /></template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    {\n      code: '<template><Foo @bar={{this.list.[0].name.[1].foo}} /></template>',\n      output: '<template><Foo @bar={{get this.list \"0.name.1.foo\"}} /></template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    // Regression: ember-template-lint#2926 — multi-line must NOT duplicate lines\n    {\n      code: `<template><Component\n  title=\"<error>\"\n  id={{@model.0.id}}\n  as |value|\n></Component></template>`,\n      output: `<template><Component\n  title=\"<error>\"\n  id={{get @model \"0.id\"}}\n  as |value|\n></Component></template>`,\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n    // Regression: ember-template-lint#2924 — must NOT eat preceding params\n    {\n      code: '<template><Button @onClick={{fn this.myFunc @row.0.sha256}}>Button</Button></template>',\n      output:\n        '<template><Button @onClick={{fn this.myFunc (get @row \"0.sha256\")}}>Button</Button></template>',\n      errors: [{ messageId: 'noObscureArrayAccess' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-obscure-array-access', rule, {\n  valid: [\n    \"{{foo bar=(get this 'list.0' )}}\",\n    \"<Foo @bar={{get this 'list.0'}}\",\n    \"{{get this 'list.0'}}\",\n    '{{foo bar @list}}',\n    'Just a regular text in the template bar.[1] bar.1',\n    '<Foo foo=\"bar.[1]\" />',\n    `<FooBar\n    @subHeaderText={{if\n      this.isFooBarV2Enabled\n      \"foobar\"\n    }}\n  />`,\n  ],\n  invalid: [\n    {\n      code: '<Foo @onClick={{fn this.func @foo.0.bar}} />',\n      output: '<Foo @onClick={{fn this.func (get @foo \"0.bar\")}} />',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"@foo.0.bar\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n    {\n      code: '{{foo bar=this.list.[0]}}',\n      output: '{{foo bar=(get this.list \"0\")}}',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"this.list.[0]\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n    {\n      code: '{{foo bar=@list.[1]}}',\n      output: '{{foo bar=(get @list \"1\")}}',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"@list.[1]\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n    {\n      code: '{{this.list.[0]}}',\n      output: '{{get this.list \"0\"}}',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"this.list.[0]\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n    {\n      code: '{{this.list.[0].name}}',\n      output: '{{get this.list \"0.name\"}}',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"this.list.[0].name\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n    {\n      code: '<Foo @bar={{this.list.[0]}} />',\n      output: '<Foo @bar={{get this.list \"0\"}} />',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"this.list.[0]\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n    {\n      code: '<Foo @bar={{this.list.[0].name.[1].foo}} />',\n      output: '<Foo @bar={{get this.list \"0.name.1.foo\"}} />',\n      errors: [\n        {\n          message:\n            'Unexpected obscure array access pattern \"this.list.[0].name.[1].foo\". Use computed properties or helpers instead.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-obsolete-elements.js",
    "content": "const rule = require('../../../lib/rules/template-no-obsolete-elements');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-obsolete-elements', rule, {\n  valid: [\n    '<template><div></div></template>',\n    `<template>{{#let (component 'whatever-here') as |plaintext|}}\n      <plaintext />\n    {{/let}}</template>`,\n    // Element-level block params (<Comp as |...|>) are now tracked\n    '<template><Comp as |plaintext|><plaintext /></Comp></template>',\n    '<template><Outer as |marquee|><marquee /></Outer></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><marquee></marquee></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><big></big></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><blink></blink></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><center></center></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><font></font></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><frame></frame></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><frameset></frameset></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><strike></strike></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    {\n      code: '<template><tt></tt></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete' }],\n    },\n    // Element's own block params must not shadow its own tag name.\n    {\n      code: '<template><marquee as |marquee|></marquee></template>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'marquee' } }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-obsolete-elements', rule, {\n  valid: [\n    '<div></div>',\n    `{{#let (component 'whatever-here') as |plaintext|}}\n      <plaintext />\n    {{/let}}`,\n  ],\n  invalid: [\n    {\n      code: '<acronym></acronym>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'acronym' } }],\n    },\n    {\n      code: '<applet></applet>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'applet' } }],\n    },\n    {\n      code: '<basefont></basefont>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'basefont' } }],\n    },\n    {\n      code: '<bgsound></bgsound>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'bgsound' } }],\n    },\n    {\n      code: '<big></big>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'big' } }],\n    },\n    {\n      code: '<blink></blink>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'blink' } }],\n    },\n    {\n      code: '<center></center>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'center' } }],\n    },\n    {\n      code: '<dir></dir>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'dir' } }],\n    },\n    {\n      code: '<font></font>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'font' } }],\n    },\n    {\n      code: '<frame></frame>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'frame' } }],\n    },\n    {\n      code: '<frameset></frameset>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'frameset' } }],\n    },\n    {\n      code: '<isindex></isindex>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'isindex' } }],\n    },\n    {\n      code: '<keygen>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'keygen' } }],\n    },\n    {\n      code: '<listing></listing>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'listing' } }],\n    },\n    {\n      code: '<marquee></marquee>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'marquee' } }],\n    },\n    {\n      code: '<menuitem></menuitem>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'menuitem' } }],\n    },\n    {\n      code: '<multicol></multicol>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'multicol' } }],\n    },\n    {\n      code: '<nextid></nextid>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'nextid' } }],\n    },\n    {\n      code: '<nobr></nobr>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'nobr' } }],\n    },\n    {\n      code: '<noembed></noembed>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'noembed' } }],\n    },\n    {\n      code: '<noframes></noframes>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'noframes' } }],\n    },\n    {\n      code: '<param>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'param' } }],\n    },\n    {\n      code: '<plaintext></plaintext>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'plaintext' } }],\n    },\n    {\n      code: '<rb></rb>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'rb' } }],\n    },\n    {\n      code: '<rtc></rtc>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'rtc' } }],\n    },\n    {\n      code: '<spacer></spacer>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'spacer' } }],\n    },\n    {\n      code: '<strike></strike>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'strike' } }],\n    },\n    {\n      code: '<tt></tt>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'tt' } }],\n    },\n    {\n      code: '<xmp></xmp>',\n      output: null,\n      errors: [{ messageId: 'obsolete', data: { element: 'xmp' } }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-only-default-slot.js",
    "content": "const rule = require('../../../lib/rules/template-no-only-default-slot');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-only-default-slot', rule, {\n  valid: [\n    `<template><MyComponent>\n      Hello!\n    </MyComponent></template>`,\n    `<template><MyComponent>\n      <:header>header</:header>\n      <:footer>footer</:footer>\n    </MyComponent></template>`,\n    `<template><MyComponent>\n      <:default>header</:default>\n      <:footer>footer</:footer>\n    </MyComponent></template>`,\n    `<template><MyComponent>\n      <:footer>footer</:footer>\n    </MyComponent></template>`,\n  ],\n  invalid: [\n    {\n      code: '<template><MyComponent><:default>what</:default></MyComponent></template>',\n      output: '<template><MyComponent>what</MyComponent></template>',\n      errors: [\n        {\n          message:\n            'Only default slot used — prefer direct block content without <:default> for clarity and simplicity.',\n        },\n      ],\n    },\n    {\n      code: '<template><MyComponent><:default></:default></MyComponent></template>',\n      output: '<template><MyComponent></MyComponent></template>',\n      errors: [\n        {\n          message:\n            'Only default slot used — prefer direct block content without <:default> for clarity and simplicity.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-only-default-slot', rule, {\n  valid: [\n    `<MyComponent>\n      Hello!\n    </MyComponent>`,\n    `<MyComponent>\n      <:header>header</:header>\n      <:footer>footer</:footer>\n    </MyComponent>`,\n    `<MyComponent>\n      <:default>header</:default>\n      <:footer>footer</:footer>\n    </MyComponent>`,\n    `<MyComponent>\n      <:footer>footer</:footer>\n    </MyComponent>`,\n  ],\n  invalid: [\n    {\n      code: '<MyComponent><:default>what</:default></MyComponent>',\n      output: '<MyComponent>what</MyComponent>',\n      errors: [\n        {\n          message:\n            'Only default slot used — prefer direct block content without <:default> for clarity and simplicity.',\n        },\n      ],\n    },\n    {\n      code: '<MyComponent><:default></:default></MyComponent>',\n      output: '<MyComponent></MyComponent>',\n      errors: [\n        {\n          message:\n            'Only default slot used — prefer direct block content without <:default> for clarity and simplicity.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-outlet-outside-routes.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-outlet-outside-routes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-outlet-outside-routes', rule, {\n  valid: [\n    // Non-outlet usage in components\n    '<template><div>Content</div></template>',\n    '<template>{{foo}}</template>',\n    '<template>{{button}}</template>',\n    // GJS route templates — outlet is allowed\n    {\n      filename: 'app/routes/foo.gjs',\n      code: '<template>{{outlet}}</template>',\n    },\n    {\n      filename: 'app/routes/foo.gts',\n      code: '<template>{{outlet}}</template>',\n    },\n    // Block form in GJS route templates — outlet is allowed\n    {\n      filename: 'app/routes/foo.gjs',\n      code: '<template>{{#outlet}}content{{/outlet}}</template>',\n    },\n    // GJS/GTS: imported JS bindings are not flagged\n    {\n      filename: 'app/components/my-component.gjs',\n      code: `import outlet from './my-outlet';\nexport default <template>{{outlet}}</template>;`,\n    },\n    {\n      filename: 'app/components/my-component.gts',\n      code: `import outlet from '@company/ui';\nexport default <template>{{outlet}}</template>;`,\n    },\n  ],\n  invalid: [\n    // Co-located component (explicit filename)\n    {\n      filename: 'app/components/my-component.gjs',\n      code: '<template>{{outlet}}</template>',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    {\n      filename: 'app/components/my-component.gts',\n      code: '<template>{{outlet}}</template>',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    {\n      filename: 'app/components/my-component.gjs',\n      code: '<template><div>{{outlet}}</div></template>',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    // Block form in component\n    {\n      filename: 'app/components/my-component.gjs',\n      code: '<template>{{#outlet}}content{{/outlet}}</template>',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-outlet-outside-routes', rule, {\n  valid: [\n    // Non-outlet usage\n    '{{foo}}',\n    '{{button}}',\n    // Route templates — outlet is allowed\n    {\n      filename: 'app/templates/foo/route.hbs',\n      code: '{{outlet}}',\n    },\n    {\n      filename: 'app/templates/routes/foo.hbs',\n      code: '{{outlet}}',\n    },\n    // Block form in route templates\n    {\n      filename: 'app/templates/foo/route.hbs',\n      code: '{{#outlet}}Why?!{{/outlet}}',\n    },\n    {\n      filename: 'app/templates/routes/foo.hbs',\n      code: '{{#outlet}}Why?!{{/outlet}}',\n    },\n    // Ambiguous path — not clearly a component, so allowed\n    {\n      filename: 'app/templates/something/foo.hbs',\n      code: '{{#outlet}}Works because ambiguous{{/outlet}}',\n    },\n    // \"components\" appears in prefix but not under <app>/templates/components/ or <app>/components/\n    {\n      filename: 'components/templates/application.hbs',\n      code: '{{outlet}}',\n    },\n  ],\n  invalid: [\n    // Classic component template\n    {\n      filename: 'app/templates/components/foo/layout.hbs',\n      code: '{{outlet}}',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    // Partial (basename starts with '-')\n    {\n      filename: 'app/templates/foo/-mything.hbs',\n      code: '{{outlet}}',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    // Co-located component template\n    {\n      filename: 'app/components/foo/layout.hbs',\n      code: '{{outlet}}',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    // Nested outlet in component\n    {\n      filename: 'app/components/foo/layout.hbs',\n      code: '<div>{{outlet}}</div>',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n    // Block form in component template\n    {\n      filename: 'app/components/foo/layout.hbs',\n      code: '{{#outlet}}content{{/outlet}}',\n      output: null,\n      errors: [{ messageId: 'noOutletOutsideRoutes' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-page-title-component.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-page-title-component');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-page-title-component', rule, {\n  valid: [\n    `<template>\n      {{pageTitle \"My Page\"}}\n    </template>`,\n    `<template>\n      {{pageTitle this.dynamicTitle}}\n    </template>`,\n    `<template>\n      <div></div>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <PageTitle>My Page</PageTitle>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Use the `pageTitle` helper instead of the <PageTitle> component.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <PageTitle @title=\"My Page\" />\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Use the `pageTitle` helper instead of the <PageTitle> component.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <PageTitle>{{this.title}}</PageTitle>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Use the `pageTitle` helper instead of the <PageTitle> component.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-passed-in-event-handlers.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-passed-in-event-handlers');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-passed-in-event-handlers', rule, {\n  valid: [\n    `<template>\n      <MyComponent @action={{this.handleAction}} />\n    </template>`,\n    `<template>\n      <MyComponent @onSubmit={{this.handleSubmit}} />\n    </template>`,\n    `<template>\n      <button {{on \"click\" this.handleClick}}>Click</button>\n    </template>`,\n\n    '<template><Foo /></template>',\n    '<template><Foo @onClick={{this.handleClick}} /></template>',\n    '<template><Foo @onclick={{this.handleClick}} /></template>',\n    '<template><Foo @Click={{this.handleClick}} /></template>',\n    '<template><Foo @touch={{this.handleClick}} /></template>',\n    '<template><Foo @random=\"foo\" /></template>',\n    '<template><Foo @random={{true}} /></template>',\n    '<template><Input @click={{this.handleClick}} /></template>',\n    '<template><Textarea @click={{this.handleClick}} /></template>',\n\n    // mouseMove/mouseEnter/mouseLeave are not Ember classic-event aliases\n    '<template><Foo @mouseMove={{this.handleMove}} /></template>',\n    '<template><Foo @mouseEnter={{this.handleEnter}} /></template>',\n    '<template><Foo @mouseLeave={{this.handleLeave}} /></template>',\n    '<template>{{foo}}</template>',\n    '<template>{{foo onClick=this.handleClick}}</template>',\n    '<template>{{foo onclick=this.handleClick}}</template>',\n    '<template>{{foo Click=this.handleClick}}</template>',\n    '<template>{{foo touch=this.handleClick}}</template>',\n    '<template>{{foo random=\"foo\"}}</template>',\n    '<template>{{foo random=true}}</template>',\n    '<template>{{input click=this.handleClick}}</template>',\n    '<template>{{textarea click=this.handleClick}}</template>',\n\n    // ignore option — angle bracket invocation\n    {\n      code: '<template><Foo @click={{this.handleClick}} /></template>',\n      options: [{ ignore: { Foo: ['click'] } }],\n    },\n    {\n      code: '<template><Foo @click={{this.handleClick}} @submit={{this.handleSubmit}} /></template>',\n      options: [{ ignore: { Foo: ['click', 'submit'] } }],\n    },\n\n    // ignore option — curly invocation\n    {\n      code: '<template>{{foo click=this.handleClick}}</template>',\n      options: [{ ignore: { foo: ['click'] } }],\n    },\n    {\n      code: '<template>{{foo click=this.handleClick submit=this.handleSubmit}}</template>',\n      options: [{ ignore: { foo: ['click', 'submit'] } }],\n    },\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <MyComponent @click={{this.handleClick}} />\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: `<template>\n        <MyComponent @submit={{this.handleSubmit}} />\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template><Foo @click={{this.handleClick}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template><Foo @keyPress={{this.handleClick}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template><Foo @submit={{this.handleClick}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{foo click=this.handleClick}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{foo keyPress=this.handleClick}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      code: '<template>{{foo submit=this.handleClick}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n\n    // ignore option — only ignores specified component (angle bracket)\n    {\n      code: '<template><Bar @click={{this.handleClick}} /></template>',\n      output: null,\n      options: [{ ignore: { Foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n    // ignore option — only ignores specified attrs (angle bracket)\n    {\n      code: '<template><Foo @submit={{this.handleSubmit}} /></template>',\n      output: null,\n      options: [{ ignore: { Foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n    // ignore option — only ignores specified component (curly)\n    {\n      code: '<template>{{bar click=this.handleClick}}</template>',\n      output: null,\n      options: [{ ignore: { foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n    // ignore option — only ignores specified attrs (curly)\n    {\n      code: '<template>{{foo submit=this.handleSubmit}}</template>',\n      output: null,\n      options: [{ ignore: { foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-passed-in-event-handlers', rule, {\n  valid: [\n    '<Foo />',\n    '<Foo @onClick={{this.handleClick}} />',\n    '<Foo @onclick={{this.handleClick}} />',\n    '<Foo @Click={{this.handleClick}} />',\n    '<Foo @touch={{this.handleClick}} />',\n    '<Foo @random=\"foo\" />',\n    '<Foo @random={{true}} />',\n    '<Input @click={{this.handleClick}} />',\n    '<Textarea @click={{this.handleClick}} />',\n    '{{foo}}',\n    '{{foo onClick=this.handleClick}}',\n    '{{foo onclick=this.handleClick}}',\n    '{{foo Click=this.handleClick}}',\n    '{{foo touch=this.handleClick}}',\n    '{{foo random=\"foo\"}}',\n    '{{foo random=true}}',\n    '{{input click=this.handleClick}}',\n    '{{textarea click=this.handleClick}}',\n\n    // ignore option — angle bracket invocation\n    {\n      code: '<Foo @click={{this.handleClick}} />',\n      options: [{ ignore: { Foo: ['click'] } }],\n    },\n    {\n      code: '<Foo @click={{this.handleClick}} @submit={{this.handleSubmit}} />',\n      options: [{ ignore: { Foo: ['click', 'submit'] } }],\n    },\n\n    // ignore option — curly invocation\n    {\n      code: '{{foo click=this.handleClick}}',\n      options: [{ ignore: { foo: ['click'] } }],\n    },\n    {\n      code: '{{foo click=this.handleClick submit=this.handleSubmit}}',\n      options: [{ ignore: { foo: ['click', 'submit'] } }],\n    },\n  ],\n  invalid: [\n    {\n      code: '<Foo @click={{this.handleClick}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Event handler \"@click\" should not be passed as a component argument. Use the `on` modifier instead.',\n        },\n      ],\n    },\n    {\n      code: '<Foo @keyPress={{this.handleClick}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Event handler \"@keyPress\" should not be passed as a component argument. Use the `on` modifier instead.',\n        },\n      ],\n    },\n    {\n      code: '<Foo @submit={{this.handleClick}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Event handler \"@submit\" should not be passed as a component argument. Use the `on` modifier instead.',\n        },\n      ],\n    },\n    {\n      code: '{{foo click=this.handleClick}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Event handler \"@click\" should not be passed as a component argument. Use the `on` modifier instead.',\n        },\n      ],\n    },\n    {\n      code: '{{foo keyPress=this.handleClick}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Event handler \"@keyPress\" should not be passed as a component argument. Use the `on` modifier instead.',\n        },\n      ],\n    },\n    {\n      code: '{{foo submit=this.handleClick}}',\n      output: null,\n      errors: [\n        {\n          message:\n            'Event handler \"@submit\" should not be passed as a component argument. Use the `on` modifier instead.',\n        },\n      ],\n    },\n\n    // ignore option — only ignores specified component (angle bracket)\n    {\n      code: '<Bar @click={{this.handleClick}} />',\n      output: null,\n      options: [{ ignore: { Foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n    // ignore option — only ignores specified attrs (angle bracket)\n    {\n      code: '<Foo @submit={{this.handleSubmit}} />',\n      output: null,\n      options: [{ ignore: { Foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n    // ignore option — only ignores specified component (curly)\n    {\n      code: '{{bar click=this.handleClick}}',\n      output: null,\n      options: [{ ignore: { foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n    // ignore option — only ignores specified attrs (curly)\n    {\n      code: '{{foo submit=this.handleSubmit}}',\n      output: null,\n      options: [{ ignore: { foo: ['click'] } }],\n      errors: [{ messageId: 'unexpected' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-pointer-down-event-binding.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-pointer-down-event-binding');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-pointer-down-event-binding', rule, {\n  valid: [\n    '<template><button {{on \"click\" this.handleClick}}>Click</button></template>',\n    '<template><button {{on \"keydown\" this.handleKeyDown}}>Press</button></template>',\n    '<template><div {{on \"mouseup\" this.handleMouseUp}}>Content</div></template>',\n    '<template><div {{on \"pointerup\" this.handlePointerUp}}>Content</div></template>',\n    '<template><div {{action this.handler on=\"click\"}}></div></template>',\n    '<template><div {{action this.handler on=\"mouseup\"}}></div></template>',\n    // Case-insensitive: MOUSEUP is fine\n    '<template><div {{on \"MOUSEUP\" this.handler}}>Content</div></template>',\n    // onmouseup attribute is fine\n    '<template><input type=\"text\" onmouseup=\"myFunction()\"></template>',\n    // Component arguments are not flagged (could be any prop name)\n    '<template><MyComponent @mouseDown={{this.doSomething}} /></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><button {{on \"mousedown\" this.handleMouseDown}}>Click</button></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    {\n      code: '<template><div {{on \"pointerdown\" this.handlePointerDown}}>Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    // Case-insensitive\n    {\n      code: '<template><div {{on \"MouseDown\" this.handler}}>Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    // HTML attributes\n    {\n      code: '<template><div onmousedown={{this.handleMouseDown}}>Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerAttrNode' }],\n    },\n    {\n      code: '<template><input type=\"text\" onmousedown=\"myFunction()\"></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerAttrNode' }],\n    },\n    {\n      code: '<template><div onpointerdown={{this.handlePointerDown}}>Content</div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerAttrNode' }],\n    },\n    // {{action}} modifier with on= hash pair\n    {\n      code: '<template><div {{action this.handler on=\"mousedown\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    {\n      code: '<template><div {{action this.handler on=\"pointerdown\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    // on= is not the first hash pair\n    {\n      code: '<template><div {{action this.handler preventDefault=true on=\"mousedown\"}}></div></template>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-pointer-down-event-binding', rule, {\n  valid: [\n    '<div {{on \"mouseup\" this.doSomething}}></div>',\n    '<div {{action this.doSomething on=\"mouseup\"}}></div>',\n    '<input type=\"text\" onmouseup=\"myFunction()\">',\n    // Component arguments are not flagged\n    '{{my-component mouseDown=this.doSomething}}',\n    '<MyComponent @mouseDown={{this.doSomething}} />',\n  ],\n  invalid: [\n    {\n      code: '<div {{on \"mousedown\" this.doSomething}}></div>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    {\n      code: '<div {{action this.doSomething on=\"mousedown\"}}></div>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    // on= is not the first hash pair\n    {\n      code: '<div {{action this.doSomething preventDefault=true on=\"mousedown\"}}></div>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n    {\n      code: '<input type=\"text\" onmousedown=\"myFunction()\">',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerAttrNode' }],\n    },\n    {\n      code: '<div {{on \"pointerdown\" this.doSomething}}></div>',\n      output: null,\n      errors: [{ messageId: 'unexpected', type: 'GlimmerElementModifierStatement' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-positional-data-test-selectors.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-positional-data-test-selectors');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-positional-data-test-selectors', rule, {\n  valid: [\n    `<template>\n      {{#if data-test-foo}}\n      {{/if}}\n    </template>`,\n    `<template>\n      <div data-test-blah></div>\n    </template>`,\n    `<template>\n      <Foo data-test-derp />\n    </template>`,\n    `<template>\n      {{something data-test-lol=true}}\n    </template>`,\n    `<template>\n      {{#if dataSomething}}\n        <div> hello </div>\n      {{/if}}\n    </template>`,\n    `<template>\n      <div\n        data-test-msg-connections-typeahead-result={{true}}\n      >\n      </div>\n    </template>`,\n    `<template>\n      <div\n        data-test-msg-connections-typeahead-result=\"foo-bar\"\n      >\n      </div>\n    </template>`,\n    `<template>\n      {{badge\n        data-test-profile-card-one-to-one-connection-distance=true\n        degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n        degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n      }}\n    </template>`,\n    `<template>\n      {{badge\n        data-test-profile-card-one-to-one-connection-distance=\"foo-bar\"\n        degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n        degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n      }}\n    </template>`,\n    `<template>\n      <div\n        data-test-profile=true\n      >\n        hello\n      </div>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        {{badge\n          data-test-profile-card-one-to-one-connection-distance\n          degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n          degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n        }}\n      </template>`,\n      output: `<template>\n        {{badge\n          data-test-profile-card-one-to-one-connection-distance=true\n          degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n          degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n        }}\n      </template>`,\n      errors: [\n        {\n          message:\n            'Passing a `data-test-*` positional param to a curly invocation should be avoided.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-positional-data-test-selectors', rule, {\n  valid: [\n    `\n      {{#if data-test-foo}}\n      {{/if}}\n    `,\n    `\n      <div data-test-blah></div>\n    `,\n    `\n      <Foo data-test-derp />\n    `,\n    `\n      {{something data-test-lol=true}}\n    `,\n    `\n      {{#if dataSomething}}\n        <div> hello </div>\n      {{/if}}\n    `,\n    `\n      <div\n        data-test-msg-connections-typeahead-result={{true}}\n      >\n      </div>\n    `,\n    `\n      <div\n        data-test-msg-connections-typeahead-result=\"foo-bar\"\n      >\n      </div>\n    `,\n    `\n      {{badge\n        data-test-profile-card-one-to-one-connection-distance=true\n        degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n        degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n      }}\n    `,\n    `\n      {{badge\n        data-test-profile-card-one-to-one-connection-distance=\"foo-bar\"\n        degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n        degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n      }}\n    `,\n    `\n      <div\n        data-test-profile=true\n      >\n        hello\n      </div>\n    `,\n  ],\n  invalid: [\n    {\n      code: `\n        {{badge\n          data-test-profile-card-one-to-one-connection-distance\n          degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n          degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n        }}\n      `,\n      output: `\n        {{badge\n          data-test-profile-card-one-to-one-connection-distance=true\n          degreeText=(t \"i18n_distance_v2\" distance=recipientDistance)\n          degreeA11yText=(t \"i18n_distance_a11y_v2\" distance=recipientDistance)\n        }}\n      `,\n      errors: [\n        {\n          message:\n            'Passing a `data-test-*` positional param to a curly invocation should be avoided.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-positive-tabindex.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-positive-tabindex');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-positive-tabindex', rule, {\n  valid: [\n    '<template><button tabindex=\"0\"></button></template>',\n    '<template><button tabindex=\"-1\"></button></template>',\n    '<template><button tabindex={{-1}}>baz</button></template>',\n    '<template><button tabindex={{\"-1\"}}>baz</button></template>',\n    '<template><button tabindex=\"{{-1}}\">baz</button></template>',\n    '<template><button tabindex=\"{{\"-1\"}}\">baz</button></template>',\n    '<template><button tabindex=\"{{if this.show -1}}\">baz</button></template>',\n    '<template><button tabindex=\"{{if this.show \"-1\" \"0\"}}\">baz</button></template>',\n    '<template><button tabindex=\"{{if (not this.show) \"-1\" \"0\"}}\">baz</button></template>',\n    '<template><button tabindex={{if this.show -1}}>baz</button></template>',\n    '<template><button tabindex={{if this.show \"-1\" \"0\"}}>baz</button></template>',\n    '<template><button tabindex={{if (not this.show) \"-1\" \"0\"}}>baz</button></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><button tabindex={{someProperty}}></button></template>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<template><button tabindex=\"1\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"text\"></button></template>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<template><button tabindex={{true}}></button></template>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{false}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{5}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{if a 1 -1}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{if a -1 1}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{if a 1}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{if (not a) 1}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{unless a 1}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<template><button tabindex=\"{{unless a -1 1}}\"></button></template>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-positive-tabindex', rule, {\n  valid: [\n    '<button tabindex=\"0\"></button>',\n    '<button tabindex=\"-1\"></button>',\n    '<button tabindex={{-1}}>baz</button>',\n    '<button tabindex={{\"-1\"}}>baz</button>',\n    '<button tabindex=\"{{-1}}\">baz</button>',\n    '<button tabindex=\"{{\"-1\"}}\">baz</button>',\n    '<button tabindex=\"{{if this.show -1}}\">baz</button>',\n    '<button tabindex=\"{{if this.show \"-1\" \"0\"}}\">baz</button>',\n    '<button tabindex=\"{{if (not this.show) \"-1\" \"0\"}}\">baz</button>',\n    '<button tabindex={{if this.show -1}}>baz</button>',\n    '<button tabindex={{if this.show \"-1\" \"0\"}}>baz</button>',\n    '<button tabindex={{if (not this.show) \"-1\" \"0\"}}>baz</button>',\n  ],\n  invalid: [\n    {\n      code: '<button tabindex={{someProperty}}></button>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<button tabindex=\"1\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"text\"></button>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<button tabindex={{true}}></button>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<button tabindex=\"{{false}}\"></button>',\n      output: null,\n      errors: [{ message: 'Tabindex values must be negative numeric.' }],\n    },\n    {\n      code: '<button tabindex=\"{{5}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"{{if a 1 -1}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"{{if a -1 1}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"{{if a 1}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"{{if (not a) 1}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"{{unless a 1}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n    {\n      code: '<button tabindex=\"{{unless a -1 1}}\"></button>',\n      output: null,\n      errors: [{ message: 'Avoid positive integer values for tabindex.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-potential-path-strings.js",
    "content": "const { RuleTester } = require('eslint');\nconst rule = require('../../../lib/rules/template-no-potential-path-strings');\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-potential-path-strings', rule, {\n  valid: [\n    '<template><img src=\"foo.png\"></template>',\n    '<template><img src={{picture}}></template>',\n    '<template><img src={{this.picture}}></template>',\n    '<template><img src={{@img}}></template>',\n    '<template><SomeComponent @foo={{@bar}} /></template>',\n    '<template><Ui::Demo @title=\"@my-org/my-package\" /></template>',\n    '<template><Ui::Demo @title=\"@my-org\\\\my-package\" /></template>',\n    '<template><Ui::Demo @title=\"@my-org|my-package\" /></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><img src=\"this.picture\"></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?',\n        },\n      ],\n    },\n    {\n      code: '<template><img src=this.picture></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?',\n        },\n      ],\n    },\n    {\n      code: '<template><img src=\"@img\"></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{@img}}?',\n        },\n      ],\n    },\n    {\n      code: '<template><img src=@img></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{@img}}?',\n        },\n      ],\n    },\n    {\n      code: '<template><SomeComponent @foo=@bar /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{@bar}}?',\n        },\n      ],\n    },\n    {\n      code: '<template><SomeComponent @foo=this.bar /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{this.bar}}?',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-potential-path-strings', rule, {\n  valid: [\n    '<img src=\"foo.png\">',\n    '<img src={{picture}}>',\n    '<img src={{this.picture}}>',\n    '<img src={{@img}}>',\n    '<SomeComponent @foo={{@bar}} />',\n    '<Ui::Demo @title=\"@my-org/my-package\" />',\n    '<Ui::Demo @title=\"@my-org\\\\my-package\" />',\n    '<Ui::Demo @title=\"@my-org|my-package\" />',\n  ],\n  invalid: [\n    {\n      code: '<img src=\"this.picture\">',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?',\n        },\n      ],\n    },\n    {\n      code: '<img src=this.picture>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{this.picture}}?',\n        },\n      ],\n    },\n    {\n      code: '<img src=\"@img\">',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{@img}}?',\n        },\n      ],\n    },\n    {\n      code: '<img src=@img>',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{@img}}?',\n        },\n      ],\n    },\n    {\n      code: '<SomeComponent @foo=@bar />',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{@bar}}?',\n        },\n      ],\n    },\n    {\n      code: '<SomeComponent @foo=this.bar />',\n      output: null,\n      errors: [\n        {\n          message: 'Potential path in attribute string detected. Did you mean {{this.bar}}?',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-quoteless-attributes.js",
    "content": "const rule = require('../../../lib/rules/template-no-quoteless-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-quoteless-attributes', rule, {\n  valid: [\n    '<template><div class=\"foo\"></div></template>',\n    '<template><div data-foo=\"derp\"></div></template>',\n    '<template><div data-foo=\"derp {{stuff}}\"></div></template>',\n    '<template><div data-foo={{someValue}}></div></template>',\n    '<template><div data-foo={{true}}></div></template>',\n    '<template><div data-foo={{false}}></div></template>',\n    '<template><div data-foo={{5}}></div></template>',\n    '<template><SomeThing ...attributes /></template>',\n    '<template><div></div></template>',\n    '<template><input disabled></template>',\n    '<template><div data-x=\"foo=bar\"></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div class=foo></div></template>',\n      output: '<template><div class=\"foo\"></div></template>',\n      errors: [{ messageId: 'missing' }],\n    },\n\n    {\n      code: '<template><div data-foo=asdf></div></template>',\n      output: '<template><div data-foo=\"asdf\"></div></template>',\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><SomeThing @blah=asdf /></template>',\n      output: '<template><SomeThing @blah=\"asdf\" /></template>',\n      errors: [{ messageId: 'missing' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-quoteless-attributes', rule, {\n  valid: [\n    '<div data-foo=\"derp\"></div>',\n    '<div data-foo=\"derp {{stuff}}\"></div>',\n    '<div data-foo={{someValue}}></div>',\n    '<div data-foo={{true}}></div>',\n    '<div data-foo={{false}}></div>',\n    '<div data-foo={{5}}></div>',\n    '<SomeThing ...attributes />',\n    '<div></div>',\n    '<input disabled>',\n    '<div data-x=\"foo=bar\"></div>',\n  ],\n  invalid: [\n    {\n      code: '<div data-foo=asdf></div>',\n      output: '<div data-foo=\"asdf\"></div>',\n      errors: [{ message: 'Attribute data-foo should be either quoted or wrapped in mustaches' }],\n    },\n    {\n      code: '<SomeThing @blah=asdf />',\n      output: '<SomeThing @blah=\"asdf\" />',\n      errors: [{ message: 'Argument @blah should be either quoted or wrapped in mustaches' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-redundant-fn.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-redundant-fn');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-redundant-fn', rule, {\n  valid: [\n    `<template>\n      <button {{on \"click\" this.handleClick}}>Click</button>\n    </template>`,\n    `<template>\n      <button {{on \"click\" (fn this.handleClick arg)}}>Click</button>\n    </template>`,\n    `<template>\n      <button {{on \"click\" (fn this.handleClick arg1 arg2)}}>Click</button>\n    </template>`,\n    `<template>\n      <Component @action={{this.myAction}} />\n    </template>`,\n\n    '<template><button {{on \"click\" this.handleClick}}>Click Me</button></template>',\n    '<template><button {{on \"click\" (fn this.handleClick \"foo\")}}>Click Me</button></template>',\n    '<template><SomeComponent @onClick={{this.handleClick}} /></template>',\n    '<template><SomeComponent @onClick={{fn this.handleClick \"foo\"}} /></template>',\n    '<template>{{foo bar=this.handleClick}}></template>',\n    '<template>{{foo bar=(fn this.handleClick \"foo\")}}></template>',\n\n    // `fn` is imported from a local module — not Ember's built-in helper.\n    // Should NOT be flagged even with a single argument.\n    `\n      import { fn } from './my-utils';\n      <template>\n        <button {{on \"click\" (fn this.handleClick)}}>Click</button>\n      </template>\n    `,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <button {{on \"click\" (fn this.handleClick)}}>Click</button>\n      </template>`,\n      output: `<template>\n        <button {{on \"click\" this.handleClick}}>Click</button>\n      </template>`,\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <Component @action={{fn this.save}} />\n      </template>`,\n      output: `<template>\n        <Component @action={{this.save}} />\n      </template>`,\n      errors: [\n        {\n          message: 'Unnecessary use of (fn) helper. Pass the function directly instead: this.save',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n\n    {\n      code: '<template><button {{on \"click\" (fn this.handleClick)}}>Click Me</button></template>',\n      output: '<template><button {{on \"click\" this.handleClick}}>Click Me</button></template>',\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n        },\n      ],\n    },\n    {\n      code: '<template><SomeComponent @onClick={{fn this.handleClick}} /></template>',\n      output: '<template><SomeComponent @onClick={{this.handleClick}} /></template>',\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n        },\n      ],\n    },\n    {\n      code: '<template>{{foo bar=(fn this.handleClick)}}></template>',\n      output: '<template>{{foo bar=this.handleClick}}></template>',\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-redundant-fn', rule, {\n  valid: [\n    '<button {{on \"click\" this.handleClick}}>Click Me</button>',\n    '<button {{on \"click\" (fn this.handleClick \"foo\")}}>Click Me</button>',\n    '<SomeComponent @onClick={{this.handleClick}} />',\n    '<SomeComponent @onClick={{fn this.handleClick \"foo\"}} />',\n    '{{foo bar=this.handleClick}}>',\n    '{{foo bar=(fn this.handleClick \"foo\")}}>',\n  ],\n  invalid: [\n    {\n      code: '<button {{on \"click\" (fn this.handleClick)}}>Click Me</button>',\n      output: '<button {{on \"click\" this.handleClick}}>Click Me</button>',\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n        },\n      ],\n    },\n    {\n      code: '<SomeComponent @onClick={{fn this.handleClick}} />',\n      output: '<SomeComponent @onClick={{this.handleClick}} />',\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n        },\n      ],\n    },\n    {\n      code: '{{foo bar=(fn this.handleClick)}}>',\n      output: '{{foo bar=this.handleClick}}>',\n      errors: [\n        {\n          message:\n            'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-redundant-role.js",
    "content": "const rule = require('../../../lib/rules/template-no-redundant-role');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-redundant-role', rule, {\n  valid: [\n    '<template><a role=\"link\" aria-disabled=\"true\">valid</a></template>',\n    '<template><form role=\"search\"></form></template>',\n    '<template><footer role={{this.foo}}></footer></template>',\n    '<template><footer role=\"{{this.stuff}}{{this.foo}}\"></footer></template>',\n    '<template><nav role=\"navigation\"></nav></template>',\n    '<template><ol role=\"list\"></ol></template>',\n    '<template><ul role=\"list\"></ul></template>',\n    {\n      code: '<template><body role=\"document\"></body></template>',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    {\n      code: '<template><footer role={{this.bar}}></footer></template>',\n      options: [{ checkAllHTMLElements: true }],\n    },\n    {\n      code: '<template><nav class=\"navigation\" role=\"navigation\"></nav></template>',\n      options: [{ checkAllHTMLElements: true }],\n    },\n    {\n      code: '<template><button role=\"link\"></button></template>',\n      options: [{ checkAllHTMLElements: true }],\n    },\n    {\n      code: '<template><input type=\"checkbox\" value=\"yes\" checked /></template>',\n      options: [{ checkAllHTMLElements: true }],\n    },\n    {\n      code: '<template><input type=\"range\" /></template>',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    {\n      code: '<template><dialog role=\"dialog\" /></template>',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    {\n      code: '<template><ul class=\"list\" role=\"combobox\"></ul></template>',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    '<template><input role=\"combobox\"></template>',\n    // <select multiple> has implicit role listbox, so combobox is not redundant.\n    '<template><select role=\"combobox\" multiple></select></template>',\n    // <select size=\"5\"> (size > 1) has implicit role listbox.\n    '<template><select role=\"combobox\" size=\"5\"></select></template>',\n    // Default <select> (no `multiple`, `size` absent or <= 1) has implicit\n    // role \"combobox\" — an explicit `role=\"listbox\"` overrides to listbox\n    // and is NOT redundant.\n    '<template><select role=\"listbox\"></select></template>',\n    '<template><select role=\"listbox\" size=\"1\"></select></template>',\n    // Dynamic `multiple={{...}}` — can't determine implicit role statically,\n    // so neither `role=\"combobox\"` nor `role=\"listbox\"` is flagged.\n    '<template><select role=\"combobox\" multiple={{this.isMulti}}></select></template>',\n    '<template><select role=\"listbox\" multiple={{this.isMulti}}></select></template>',\n\n    // Role-fallback: first recognised token wins. `role=\"tab button\"` on\n    // <button> resolves to `tab` (non-redundant — button's implicit is\n    // `button`, not `tab`). WAI-ARIA §4.1 fallback-list semantics.\n    '<template><button role=\"tab button\"></button></template>',\n    // `region` is a landmark role, but <div> has no implicit role — role=\"region\"\n    // on a <div> is not redundant.\n    '<template><div role=\"region\"></div></template>',\n    {\n      code: '<template><div role=\"region\"></div></template>',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    // Per #2694: <section> has implicit role `generic` when unnamed and\n    // `region` when it has an accessible name. role=\"region\" on <section> is\n    // therefore not unconditionally redundant — #38 aligns with this.\n    '<template><section role=\"region\" aria-label=\"Quick facts\">...</section></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><dialog role=\"dialog\" /></template>',\n      output: '<template><dialog /></template>',\n      errors: [\n        {\n          message: 'Use of redundant or invalid role: dialog on <dialog> detected.',\n        },\n      ],\n    },\n    {\n      code: '<template><header role=\"banner\"></header></template>',\n      output: '<template><header></header></template>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: banner on <header> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    // Non-landmark same-role redundancy — covered by jsx-a11y / vue-a11y too.\n    {\n      code: '<template><button role=\"button\"></button></template>',\n      output: '<template><button></button></template>',\n      errors: [{ message: 'Use of redundant or invalid role: button on <button> detected.' }],\n    },\n    {\n      code: '<template><img role=\"img\" /></template>',\n      output: '<template><img /></template>',\n      errors: [{ message: 'Use of redundant or invalid role: img on <img> detected.' }],\n    },\n    {\n      // Valueless `<select size>` — per HTML boolean-attr semantics, the\n      // attribute value is an empty string; Number('') is 0; 0 is NOT > 1,\n      // so the implicit role stays combobox. `role=\"combobox\"` is therefore\n      // redundant and must be flagged.\n      code: '<template><select role=\"combobox\" size></select></template>',\n      output: '<template><select size></select></template>',\n      errors: [\n        {\n          message: 'Use of redundant or invalid role: combobox on <select> detected.',\n        },\n      ],\n    },\n    {\n      // Role-fallback: unknown leading token is skipped per ARIA §4.1.\n      // `role=\"xxyxyz button\"` resolves to `button`, which IS redundant on\n      // <button>. Autofix drops the whole role attribute — the implicit\n      // `button` role is preserved natively, so runtime semantics are\n      // unchanged. Authors who wanted the `xxyxyz` fallback for some\n      // reason can opt out via eslint-disable.\n      code: '<template><button role=\"xxyxyz button\"></button></template>',\n      output: '<template><button></button></template>',\n      errors: [\n        {\n          message: 'Use of redundant or invalid role: button on <button> detected.',\n        },\n      ],\n    },\n    {\n      code: '<template><main role=\"main\"></main></template>',\n      output: '<template><main></main></template>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: main on <main> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<template><aside role=\"complementary\"></aside></template>',\n      output: '<template><aside></aside></template>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: complementary on <aside> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<template><footer role=\"contentinfo\"></footer></template>',\n      output: '<template><footer></footer></template>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: contentinfo on <footer> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<template><form role=\"form\"></form></template>',\n      output: '<template><form></form></template>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: form on <form> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n\n    {\n      code: '<template><header role=\"banner\" class=\"page-header\"></header></template>',\n      output: '<template><header class=\"page-header\"></header></template>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: banner on <header> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-redundant-role', rule, {\n  valid: [\n    '<a role=\"link\" aria-disabled=\"true\">valid</a>',\n    '<form role=\"search\"></form>',\n    '<footer role={{this.foo}}></footer>',\n    '<footer role=\"{{this.stuff}}{{this.foo}}\"></footer>',\n    '<nav role=\"navigation\"></nav>',\n    '<ol role=\"list\"></ol>',\n    '<ul role=\"list\"></ul>',\n    '<input role=\"combobox\">',\n    '<footer role={{this.bar}}></footer>',\n    '<nav class=\"navigation\" role=\"navigation\"></nav>',\n    '<button role=\"link\"></button>',\n    '<input type=\"checkbox\" value=\"yes\" checked />',\n    '<input type=\"range\" />',\n    {\n      code: '<body role=\"document\"></body>',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    {\n      code: '<dialog role=\"dialog\" />',\n      options: [{ checkAllHTMLElements: false }],\n    },\n    '<ul class=\"list\" role=\"combobox\"></ul>',\n    // <select> with `multiple` has implicit role \"listbox\", so role=\"combobox\"\n    // is not redundant (it disagrees with the implicit role, but that is for\n    // other rules to catch — this rule only flags redundancy).\n    '<select role=\"combobox\" multiple></select>',\n    // <select size=\"5\"> (size > 1) has implicit role \"listbox\", same reasoning.\n    '<select role=\"combobox\" size=\"5\"></select>',\n    // Default <select> (no `multiple`, `size` absent or <= 1) has implicit\n    // role \"combobox\" — explicit role=\"listbox\" overrides to listbox and is\n    // NOT redundant.\n    '<select role=\"listbox\"></select>',\n    '<select role=\"listbox\" size=\"1\"></select>',\n    // Per #2694: <section> has implicit role `generic` when unnamed and\n    // `region` when it has an accessible name. role=\"region\" on <section> is\n    // therefore not unconditionally redundant — #38 aligns with this.\n    '<section role=\"region\" aria-label=\"Quick facts\">...</section>',\n  ],\n  invalid: [\n    {\n      code: '<header role=\"banner\"></header>',\n      output: '<header></header>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: banner on <header> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<footer role=\"contentinfo\"></footer>',\n      output: '<footer></footer>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: contentinfo on <footer> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<main role=\"main\"></main>',\n      output: '<main></main>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: main on <main> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<aside role=\"complementary\"></aside>',\n      output: '<aside></aside>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: complementary on <aside> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<form role=\"form\"></form>',\n      output: '<form></form>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: form on <form> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<header role=\"banner\" class=\"page-header\"></header>',\n      output: '<header class=\"page-header\"></header>',\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: banner on <header> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<dialog role=\"dialog\" />',\n      output: '<dialog />',\n      errors: [{ message: 'Use of redundant or invalid role: dialog on <dialog> detected.' }],\n    },\n    {\n      code: '<button role=\"button\"></button>',\n      output: '<button></button>',\n      errors: [{ message: 'Use of redundant or invalid role: button on <button> detected.' }],\n    },\n    {\n      code: '<input type=\"checkbox\" name=\"agree\" value=\"checkbox1\" role=\"checkbox\" />',\n      output: '<input type=\"checkbox\" name=\"agree\" value=\"checkbox1\" />',\n      errors: [{ message: 'Use of redundant or invalid role: checkbox on <input> detected.' }],\n    },\n    {\n      code: '<table><th role=\"columnheader\">Some heading</th><td>cell1</td></table>',\n      output: '<table><th>Some heading</th><td>cell1</td></table>',\n      errors: [{ message: 'Use of redundant or invalid role: columnheader on <th> detected.' }],\n    },\n    {\n      code: '<select name=\"color\" id=\"color\" role=\"listbox\" multiple><option value=\"default-color\">black</option></select>',\n      output:\n        '<select name=\"color\" id=\"color\" multiple><option value=\"default-color\">black</option></select>',\n      errors: [{ message: 'Use of redundant or invalid role: listbox on <select> detected.' }],\n    },\n    {\n      // <select> without `multiple` or `size` defaults to role \"combobox\".\n      code: '<select role=\"combobox\"></select>',\n      output: '<select></select>',\n      errors: [{ message: 'Use of redundant or invalid role: combobox on <select> detected.' }],\n    },\n    {\n      // size=\"1\" still defaults to combobox (only size > 1 flips to listbox).\n      code: '<select role=\"combobox\" size=\"1\"></select>',\n      output: '<select size=\"1\"></select>',\n      errors: [{ message: 'Use of redundant or invalid role: combobox on <select> detected.' }],\n    },\n    {\n      // Case-insensitive match on <select>, combined with the implicit-role check.\n      code: '<select role=\"COMBOBOX\"></select>',\n      output: '<select></select>',\n      errors: [{ message: 'Use of redundant or invalid role: combobox on <select> detected.' }],\n    },\n    {\n      // Case-insensitive matching — ARIA role tokens compare as ASCII-case-insensitive.\n      code: '<body role=\"DOCUMENT\"></body>',\n      output: '<body></body>',\n      errors: [{ message: 'Use of redundant or invalid role: document on <body> detected.' }],\n    },\n    {\n      code: '<main role=\"main\"></main>',\n      output: '<main></main>',\n      options: [{ checkAllHTMLElements: false }],\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: main on <main> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n    {\n      code: '<aside role=\"complementary\"></aside>',\n      output: '<aside></aside>',\n      options: [{ checkAllHTMLElements: false }],\n      errors: [\n        {\n          message:\n            'Use of redundant or invalid role: complementary on <aside> detected. If a landmark element is used, any role provided will either be redundant or incorrect.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-restricted-invocations.js",
    "content": "const rule = require('../../../lib/rules/template-no-restricted-invocations');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-restricted-invocations', rule, {\n  valid: [\n    {\n      code: '<template>{{baz}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: '<template>{{baz foo=bar}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: '<template>{{#baz}}{{/baz}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: '<template>{{component \"baz\"}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: '<template>{{other-component}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: '<template><RandomComponent /></template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: '<template></template>',\n      output: null,\n      options: [['foo', 'bar']],\n    },\n\n    '<template>{{baz foo=(baz)}}</template>',\n    '<template>{{#baz foo=bar}}{{/baz}}</template>',\n    '<template>{{#baz foo=(baz)}}{{/baz}}</template>',\n    '<template>{{component}}</template>',\n    '<template>{{component \"baz\" foo=bar}}</template>',\n    '<template>{{component \"baz\" foo=(baz)}}</template>',\n    '<template>{{#component \"baz\"}}{{/component}}</template>',\n    '<template>{{#component \"baz\" foo=bar}}{{/component}}</template>',\n    '<template>{{#component \"baz\" foo=(baz)}}{{/component}}</template>',\n    '<template>{{yield (component \"baz\")}}</template>',\n    '<template>{{yield (component \"baz\" foo=bar)}}</template>',\n    '<template>{{yield (component \"baz\" foo=(baz))}}</template>',\n    '<template>{{yield (baz (baz (baz) bar))}}</template>',\n    '<template>{{yield (baz (baz (baz) (baz)))}}</template>',\n    '<template>{{yield (baz (baz (baz) foo=(baz)))}}</template>',\n    '<template>{{#baz as |foo|}}{{foo}}{{/baz}}</template>',\n    '<template>{{#with (component \"blah\") as |Foo|}} <Foo /> {{/with}}</template>',\n    '<template>{{other/foo-bar}}</template>',\n    '<template>{{nested-scope/other}}</template>',\n    '<template><Random/></template>',\n    '<template><HelloWorld/></template>',\n    '<template><NestedScope::Random/></template>',\n\n    // JS-scope variables (imports, const, let) should be exempt — same as block params.\n    {\n      code: `\n        import foo from './foo';\n        <template>{{foo}}</template>\n      `,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: `\n        import Foo from './foo';\n        <template><Foo /></template>\n      `,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: `\n        const foo = () => {};\n        <template>{{foo}}</template>\n      `,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: `\n        import foo from './foo';\n        <template>{{foo \"hello\"}}</template>\n      `,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: `\n        import bar from './bar';\n        <template>{{bar}}</template>\n      `,\n      options: [['foo', 'bar']],\n    },\n    {\n      code: `\n        import foo from './foo';\n        import bar from './bar';\n        <template>{{foo}}{{bar}}</template>\n      `,\n      options: [['foo', 'bar']],\n    },\n  ],\n  invalid: [\n    {\n      code: '<template>{{foo}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\",\n        },\n      ],\n    },\n    {\n      code: '<template>{{#foo}}{{/foo}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{#foo}}'\",\n        },\n      ],\n    },\n    {\n      code: '<template><Foo /></template>',\n      output: null,\n      options: [['foo', 'bar']],\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '<Foo />'\",\n        },\n      ],\n    },\n    {\n      code: '<template>{{bar foo=bar}}</template>',\n      output: null,\n      options: [['foo', 'bar']],\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{bar}}'\",\n        },\n      ],\n    },\n    {\n      code: '<template>{{deprecated-component}}</template>',\n      output: null,\n      options: [\n        [\n          'foo',\n          {\n            names: ['deprecated-component'],\n            message: 'Use new-component instead',\n          },\n        ],\n      ],\n      errors: [\n        {\n          message: 'Use new-component instead',\n        },\n      ],\n    },\n\n    {\n      code: '<template><div {{foo}} /></template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\" }],\n    },\n    {\n      code: '<template>{{foo foo=bar}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\" }],\n    },\n    {\n      code: '<template>{{foo foo=(baz)}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\" }],\n    },\n    {\n      code: '<template>{{#foo foo=bar}}{{/foo}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '{{#foo}}'\" }],\n    },\n    {\n      code: '<template>{{#foo foo=(baz)}}{{/foo}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '{{#foo}}'\" }],\n    },\n    {\n      code: '<template>{{component \"foo\"}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'{{component \"foo\"}}\\'' },\n      ],\n    },\n    {\n      code: '<template>{{component \"foo\" foo=bar}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'{{component \"foo\"}}\\'' },\n      ],\n    },\n    {\n      code: '<template>{{component \"foo\" foo=(baz)}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'{{component \"foo\"}}\\'' },\n      ],\n    },\n    {\n      code: '<template>{{#component \"foo\"}}{{/component}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'{{#component \"foo\"}}\\'' },\n      ],\n    },\n    {\n      code: '<template>{{#component \"foo\" foo=bar}}{{/component}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'{{#component \"foo\"}}\\'' },\n      ],\n    },\n    {\n      code: '<template>{{#component \"foo\" foo=(baz)}}{{/component}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'{{#component \"foo\"}}\\'' },\n      ],\n    },\n    {\n      code: '<template>{{yield (component \"foo\")}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'(component \"foo\")\\'' },\n      ],\n    },\n    {\n      code: '<template>{{yield (component \"foo\" foo=bar)}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'(component \"foo\")\\'' },\n      ],\n    },\n    {\n      code: '<template>{{yield (component \"foo\" foo=(baz))}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        { message: 'Cannot use disallowed helper, component or modifier \\'(component \"foo\")\\'' },\n      ],\n    },\n    {\n      code: '<template>{{yield (baz (foo (baz) bar))}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '(foo)'\" }],\n    },\n    {\n      code: '<template>{{yield (baz (baz (baz) (foo)))}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '(foo)'\" }],\n    },\n    {\n      code: '<template>{{yield (baz (baz (baz) foo=(foo)))}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '(foo)'\" }],\n    },\n    {\n      code: '<template>{{#baz as |bar|}}{{bar foo=(foo)}}{{/baz}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [{ message: \"Cannot use disallowed helper, component or modifier '(foo)'\" }],\n    },\n    {\n      code: '<template>{{nested-scope/foo-bar}}</template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{nested-scope/foo-bar}}'\",\n        },\n      ],\n    },\n    {\n      code: '<template><NestedScope::FooBar/></template>',\n      output: null,\n      options: [['foo', 'nested-scope/foo-bar']],\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '<NestedScope::FooBar />'\",\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-restricted-invocations', rule, {\n  valid: [\n    '{{baz}}',\n    '{{baz foo=bar}}',\n    '{{baz foo=(baz)}}',\n    '{{#baz}}{{/baz}}',\n    '{{#baz foo=bar}}{{/baz}}',\n    '{{#baz foo=(baz)}}{{/baz}}',\n    '{{component}}',\n    '{{component \"baz\"}}',\n    '{{component \"baz\" foo=bar}}',\n    '{{component \"baz\" foo=(baz)}}',\n    '{{#component \"baz\"}}{{/component}}',\n    '{{#component \"baz\" foo=bar}}{{/component}}',\n    '{{#component \"baz\" foo=(baz)}}{{/component}}',\n    '{{yield (component \"baz\")}}',\n    '{{yield (component \"baz\" foo=bar)}}',\n    '{{yield (component \"baz\" foo=(baz))}}',\n    '{{yield (baz (baz (baz) bar))}}',\n    '{{yield (baz (baz (baz) (baz)))}}',\n    '{{yield (baz (baz (baz) foo=(baz)))}}',\n    '{{#baz as |foo|}}{{foo}}{{/baz}}',\n    '{{#with (component \"blah\") as |Foo|}} <Foo /> {{/with}}',\n    '{{other/foo-bar}}',\n    '{{nested-scope/other}}',\n    '<Random/>',\n    '<HelloWorld/>',\n    '<NestedScope::Random/>',\n  ].map((code) => ({\n    code: typeof code === 'string' ? code : code.code,\n    options: [\n      [\n        'foo',\n        'bar',\n        'nested-scope/foo-bar',\n        {\n          names: ['deprecated-component'],\n          message: 'This component is deprecated; use component ABC instead.',\n        },\n      ],\n    ],\n  })),\n  invalid: [\n    // Modifier on element.\n    {\n      code: '<div {{foo}} />',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\",\n        },\n      ],\n    },\n    // Mustache invocations.\n    {\n      code: '{{foo}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\",\n        },\n      ],\n    },\n    {\n      code: '{{foo foo=bar}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\",\n        },\n      ],\n    },\n    {\n      code: '{{foo foo=(baz)}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{foo}}'\",\n        },\n      ],\n    },\n    // Angle bracket invocation.\n    {\n      code: '<Foo />',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '<Foo />'\",\n        },\n      ],\n    },\n    // Block invocations.\n    {\n      code: '{{#foo}}{{/foo}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{#foo}}'\",\n        },\n      ],\n    },\n    {\n      code: '{{#foo foo=bar}}{{/foo}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{#foo}}'\",\n        },\n      ],\n    },\n    {\n      code: '{{#foo foo=(baz)}}{{/foo}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{#foo}}'\",\n        },\n      ],\n    },\n    // Component helper invocations.\n    {\n      code: '{{component \"foo\"}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'{{component \"foo\"}}\\'',\n        },\n      ],\n    },\n    {\n      code: '{{component \"foo\" foo=bar}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'{{component \"foo\"}}\\'',\n        },\n      ],\n    },\n    {\n      code: '{{component \"foo\" foo=(baz)}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'{{component \"foo\"}}\\'',\n        },\n      ],\n    },\n    {\n      code: '{{#component \"foo\"}}{{/component}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'{{#component \"foo\"}}\\'',\n        },\n      ],\n    },\n    {\n      code: '{{#component \"foo\" foo=bar}}{{/component}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'{{#component \"foo\"}}\\'',\n        },\n      ],\n    },\n    {\n      code: '{{#component \"foo\" foo=(baz)}}{{/component}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'{{#component \"foo\"}}\\'',\n        },\n      ],\n    },\n    // Subexpression with component helper.\n    {\n      code: '{{yield (component \"foo\")}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'(component \"foo\")\\'',\n        },\n      ],\n    },\n    {\n      code: '{{yield (component \"foo\" foo=bar)}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'(component \"foo\")\\'',\n        },\n      ],\n    },\n    {\n      code: '{{yield (component \"foo\" foo=(baz))}}',\n      output: null,\n      errors: [\n        {\n          message: 'Cannot use disallowed helper, component or modifier \\'(component \"foo\")\\'',\n        },\n      ],\n    },\n    // Nested subexpressions.\n    {\n      code: '{{yield (baz (foo (baz) bar))}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '(foo)'\",\n        },\n      ],\n    },\n    {\n      code: '{{yield (baz (baz (baz) (foo)))}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '(foo)'\",\n        },\n      ],\n    },\n    {\n      code: '{{yield (baz (baz (baz) foo=(foo)))}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '(foo)'\",\n        },\n      ],\n    },\n    {\n      code: '{{#baz as |bar|}}{{bar foo=(foo)}}{{/baz}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '(foo)'\",\n        },\n      ],\n    },\n    // Nested scope (slash path).\n    {\n      code: '{{nested-scope/foo-bar}}',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '{{nested-scope/foo-bar}}'\",\n        },\n      ],\n    },\n    // Nested scope (angle bracket with ::).\n    {\n      code: '<NestedScope::FooBar/>',\n      output: null,\n      errors: [\n        {\n          message: \"Cannot use disallowed helper, component or modifier '<NestedScope::FooBar />'\",\n        },\n      ],\n    },\n    // Custom message from config object.\n    {\n      code: '{{deprecated-component}}',\n      output: null,\n      errors: [\n        {\n          message: 'This component is deprecated; use component ABC instead.',\n        },\n      ],\n    },\n  ].map((test) => ({\n    ...test,\n    options: [\n      [\n        'foo',\n        'bar',\n        'nested-scope/foo-bar',\n        {\n          names: ['deprecated-component'],\n          message: 'This component is deprecated; use component ABC instead.',\n        },\n      ],\n    ],\n  })),\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-route-action.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-route-action');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-route-action', rule, {\n  valid: [\n    `<template>\n      <button {{on \"click\" (fn this.action arg)}}>Click</button>\n    </template>`,\n    `<template>\n      <button {{on \"click\" this.handleClick}}>Click</button>\n    </template>`,\n    `<template>\n      {{this.routeAction}}\n    </template>`,\n    `<template>\n      <Component @action={{this.handleAction}} />\n    </template>`,\n\n    \"<template>{{custom-component onUpdate=(action 'updateFoo')}}</template>\",\n    \"<template>{{custom-component onUpdate=(fn this.updateFoo 'bar')}}</template>\",\n    '<template>{{custom-component onUpdate=this.updateFoo}}</template>',\n    \"<template><CustomComponent @onUpdate={{if true (action 'updateFoo')}} /></template>\",\n    \"<template><CustomComponent @onUpdate={{if true (fn this.updateFoo 'bar')}} /></template>\",\n    '<template><CustomComponent @onUpdate={{if true (this.updateFoo)}} /></template>',\n    `<template>{{yield (hash\n      someProp=\"someVal\"\n      updateFoo=(fn this.updateFoo)\n    )}}</template>`,\n    \"<template><CustomComponent @onUpdate={{action 'updateFoo'}} /></template>\",\n    \"<template><CustomComponent @onUpdate={{fn this.updateFoo 'bar'}} /></template>\",\n    '<template><CustomComponent @onUpdate={{this.updateFoo}} /></template>',\n    '<template><div></div></template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        {{route-action \"save\"}}\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <button {{on \"click\" (route-action \"save\")}}>Save</button>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n          type: 'GlimmerSubExpression',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <Component @action={{route-action \"update\"}} />\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n          type: 'GlimmerMustacheStatement',\n        },\n      ],\n    },\n\n    {\n      code: \"<template><CustomComponent @onUpdate={{if true (route-action 'updateFoo' 'bar')}} /></template>\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: \"<template>{{custom-component onUpdate=(route-action 'updateFoo' 'bar')}}</template>\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: `<template>{{yield (hash\n        someProp=\"someVal\"\n        updateFoo=(route-action 'updateFoo')\n      )}}</template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: `<template><CustomComponent\n        @onUpdate={{route-action 'updateFoo'}}\n      /></template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: \"<template><CustomComponent @onUpdate={{route-action 'updateBar' 'bar'}} /></template>\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-route-action', rule, {\n  valid: [\n    \"{{custom-component onUpdate=(action 'updateFoo')}}\",\n    \"{{custom-component onUpdate=(fn this.updateFoo 'bar')}}\",\n    '{{custom-component onUpdate=this.updateFoo}}',\n    \"<CustomComponent @onUpdate={{if true (action 'updateFoo')}} />\",\n    \"<CustomComponent @onUpdate={{if true (fn this.updateFoo 'bar')}} />\",\n    '<CustomComponent @onUpdate={{if true (this.updateFoo)}} />',\n    `{{yield (hash\n      someProp=\"someVal\"\n      updateFoo=(fn this.updateFoo)\n    )}}`,\n    \"<CustomComponent @onUpdate={{action 'updateFoo'}} />\",\n    \"<CustomComponent @onUpdate={{fn this.updateFoo 'bar'}} />\",\n    '<CustomComponent @onUpdate={{this.updateFoo}} />',\n    '<div></div>',\n  ],\n  invalid: [\n    {\n      code: \"<CustomComponent @onUpdate={{if true (route-action 'updateFoo' 'bar')}} />\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: \"{{custom-component onUpdate=(route-action 'updateFoo' 'bar')}}\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: `{{yield (hash\n        someProp=\"someVal\"\n        updateFoo=(route-action 'updateFoo')\n      )}}`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: `<CustomComponent\n        @onUpdate={{route-action 'updateFoo'}}\n      />`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n    {\n      code: \"<CustomComponent @onUpdate={{route-action 'updateBar' 'bar'}} />\",\n      output: null,\n      errors: [\n        {\n          message:\n            'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-scope-outside-table-headings.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-scope-outside-table-headings');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-scope-outside-table-headings', rule, {\n  valid: [\n    '<template><th scope=\"row\">Some table heading></th></template>',\n    `<template>\n    <table>\n      <th scope=\"col\">Table header</th>\n      <td>Some data</td>\n    </table>\n    </template>`,\n    '<template><CustomComponent scope /></template>',\n    '<template><CustomComponent scope=\"row\" /></template>',\n    '<template><CustomComponent scope={{foo}} /></template>',\n    '<template>{{foo-component scope=\"row\"}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template><td scope=\"row\"></td></template>',\n      output: null,\n      errors: [{ messageId: 'noScopeOutsideTableHeadings' }],\n    },\n    {\n      code: '<template><td scope></td></template>',\n      output: null,\n      errors: [{ messageId: 'noScopeOutsideTableHeadings' }],\n    },\n    {\n      code: '<template><a scope=\"row\" /></template>',\n      output: null,\n      errors: [{ messageId: 'noScopeOutsideTableHeadings' }],\n    },\n    {\n      code: `<template>\n<table>\n<th>Some header</th>\n<td scope=\"col\">Some data</td>\n</table>\n</template>`,\n      output: null,\n      errors: [{ messageId: 'noScopeOutsideTableHeadings' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-scope-outside-table-headings', rule, {\n  valid: [\n    '<th scope=\"row\">Some table heading></th>',\n    `\n    <table>\n      <th scope=\"col\">Table header</th>\n      <td>Some data</td>\n    </table>\n    `,\n    '<CustomComponent scope />',\n    '<CustomComponent scope=\"row\" />',\n    '<CustomComponent scope={{foo}} />',\n    '{{foo-component scope=\"row\"}}',\n  ],\n  invalid: [\n    {\n      code: '<td scope=\"row\"></td>',\n      output: null,\n      errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],\n    },\n    {\n      code: '<td scope></td>',\n      output: null,\n      errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],\n    },\n    {\n      code: '<a scope=\"row\" />',\n      output: null,\n      errors: [{ message: 'Unexpected scope attribute on <a>. Use only on <th>.' }],\n    },\n    {\n      code: `\n<table>\n<th>Some header</th>\n<td scope=\"col\">Some data</td>\n</table>\n`,\n      output: null,\n      errors: [{ message: 'Unexpected scope attribute on <td>. Use only on <th>.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-shadowed-elements.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-shadowed-elements');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-shadowed-elements', rule, {\n  valid: [\n    '<template><div>content</div></template>',\n    '<template><form><input /></form></template>',\n    '<template>{{#foo-bar as |Baz|}}<Baz />{{/foo-bar}}</template>',\n    '<template><FooBar as |Baz|><Baz /></FooBar></template>',\n    '<template>{{#with foo=(component \"blah-zorz\") as |Div|}}<Div></Div>{{/with}}</template>',\n    '<template><Foo as |bar|><bar.baz /></Foo></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><FooBar as |div|><div></div></FooBar></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Component name \"div\" shadows HTML element <div>. Use a different name.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    // Upstream flags any lowercase local block-param invocation, not just\n    // names present in a static html-tags list.\n    {\n      code: '<template><FooBar as |foo|><foo></foo></FooBar></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Component name \"foo\" shadows HTML element <foo>. Use a different name.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-shadowed-elements', rule, {\n  valid: [\n    '<div>content</div>',\n    '<form><input /></form>',\n    '{{#foo-bar as |Baz|}}<Baz />{{/foo-bar}}',\n    '<FooBar as |Baz|><Baz /></FooBar>',\n    '{{#with foo=(component \"blah-zorz\") as |Div|}}<Div></Div>{{/with}}',\n    '<Foo as |bar|><bar.baz /></Foo>',\n  ],\n  invalid: [\n    {\n      code: '<FooBar as |div|><div></div></FooBar>',\n      output: null,\n      errors: [\n        { message: 'Component name \"div\" shadows HTML element <div>. Use a different name.' },\n      ],\n    },\n    // Upstream flags any lowercase local block-param invocation, not just\n    // names present in a static html-tags list.\n    {\n      code: '<FooBar as |foo|><foo></foo></FooBar>',\n      output: null,\n      errors: [\n        { message: 'Component name \"foo\" shadows HTML element <foo>. Use a different name.' },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-splattributes-with-class.js",
    "content": "const rule = require('../../../lib/rules/template-no-splattributes-with-class');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE =\n  'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.';\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-splattributes-with-class', rule, {\n  valid: [\n    '<template><div ...attributes>content</div></template>',\n    '<template><div class=\"foo\">content</div></template>',\n    '<template><div class=\"foo bar\">content</div></template>',\n    '<template><div class={{foo}}>content</div></template>',\n    '<template><div class=\"foo {{bar}}\">content</div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div ...attributes class=\"foo\">content</div></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><div class=\"foo\" ...attributes>content</div></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><div ...attributes class={{foo}}>content</div></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n\n    {\n      code: '<template><div class=\"foo\" ...attributes class=\"bar\">content</div></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-splattributes-with-class', rule, {\n  valid: [\n    '<div ...attributes>content</div>',\n    '<div class=\"foo\">content</div>',\n    '<div class=\"foo bar\">content</div>',\n    '<div class={{foo}}>content</div>',\n    '<div class=\"foo {{bar}}\">content</div>',\n  ],\n  invalid: [\n    {\n      code: '<div ...attributes class=\"foo\">content</div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',\n        },\n      ],\n    },\n    {\n      code: '<div class=\"foo\" ...attributes>content</div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',\n        },\n      ],\n    },\n    {\n      code: '<div ...attributes class={{foo}}>content</div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',\n        },\n      ],\n    },\n    {\n      code: '<div class=\"foo\" ...attributes class=\"bar\">content</div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Using `...attributes` with `class` attribute is not allowed. Use `...attributes` alone to allow class merging.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-this-in-template-only-components.js",
    "content": "const path = require('node:path');\nconst rule = require('../../../lib/rules/template-no-this-in-template-only-components');\nconst RuleTester = require('eslint').RuleTester;\n\nconst FIXTURES = path.resolve(\n  __dirname,\n  '../../fixtures/template-no-this-in-template-only-components'\n);\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\nruleTester.run('template-no-this-in-template-only-components', rule, {\n  valid: [\n    '<template>{{@foo}}</template>',\n    '<template>{{welcome-page}}</template>',\n    '<template><WelcomePage /></template>',\n    '<template><MyComponent @prop={{can \"edit\" @model}} /></template>',\n    '<template>{{my-component model=model}}</template>',\n    // Class components should not be flagged (not template-only)\n    'class MyComponent extends Component { <template>{{this.foo}}</template> }',\n    'class MyComponent extends Component { <template>{{this.bar}} {{this.baz}}</template> }',\n  ],\n  invalid: [\n    {\n      code: '<template>{{this.foo}}</template>',\n      output: '<template>{{@foo}}</template>',\n      errors: [{ messageId: 'noThis' }],\n    },\n\n    {\n      code: '<template>{{my-component model=this.model}}</template>',\n      output: '<template>{{my-component model=@model}}</template>',\n      errors: [{ messageId: 'noThis' }],\n    },\n    {\n      code: '<template>{{my-component action=(action this.myAction)}}</template>',\n      output: '<template>{{my-component action=(action @myAction)}}</template>',\n      errors: [{ messageId: 'noThis' }],\n    },\n    {\n      code: '<template><MyComponent @prop={{can \"edit\" this.model}} /></template>',\n      output: '<template><MyComponent @prop={{can \"edit\" @model}} /></template>',\n      errors: [{ messageId: 'noThis' }],\n    },\n    {\n      code: '<template>{{input id=(concat this.elementId \"-username\")}}</template>',\n      output: null,\n      errors: [{ messageId: 'noThis' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-this-in-template-only-components', rule, {\n  valid: [\n    '{{welcome-page}}',\n    '<WelcomePage />',\n    '<MyComponent @prop={{can \"edit\" @model}} />',\n    '{{my-component model=model}}',\n  ],\n  invalid: [\n    {\n      code: '{{my-component model=this.model}}',\n      output: '{{my-component model=@model}}',\n      errors: [\n        {\n          message:\n            \"Usage of 'this' in path 'this.model' is not allowed in a template-only component. Use '@model' if it is a named argument.\",\n        },\n      ],\n    },\n    {\n      code: '{{my-component action=(action this.myAction)}}',\n      output: '{{my-component action=(action @myAction)}}',\n      errors: [\n        {\n          message:\n            \"Usage of 'this' in path 'this.myAction' is not allowed in a template-only component. Use '@myAction' if it is a named argument.\",\n        },\n      ],\n    },\n    {\n      code: '<MyComponent @prop={{can \"edit\" this.model}} />',\n      output: '<MyComponent @prop={{can \"edit\" @model}} />',\n      errors: [\n        {\n          message:\n            \"Usage of 'this' in path 'this.model' is not allowed in a template-only component. Use '@model' if it is a named argument.\",\n        },\n      ],\n    },\n    {\n      code: '{{input id=(concat this.elementId \"-username\")}}',\n      output: null,\n      errors: [\n        {\n          message:\n            \"Usage of 'this' in path 'this.elementId' is not allowed in a template-only component. Use '@elementId' if it is a named argument.\",\n        },\n      ],\n    },\n  ],\n});\n\n// Test .hbs files with explicit filenames to verify filesystem-based detection.\n// Route templates (app/templates/ but not app/templates/components/) should be skipped.\nconst hbsFilenameTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsFilenameTester.run('template-no-this-in-template-only-components (hbs filenames)', rule, {\n  valid: [\n    // Route templates should never be flagged (they always have a controller context)\n    {\n      filename: 'app/templates/application.hbs',\n      code: '{{this.foo}}',\n    },\n    // Nested route template path\n    {\n      filename: 'app/templates/posts/show.hbs',\n      code: '{{this.foo}}',\n    },\n    // Co-located component with a real .js companion class on disk\n    {\n      filename: path.join(FIXTURES, 'app/components/with-class.hbs'),\n      code: '{{this.foo}}',\n    },\n    // Co-located component with a real .ts companion class on disk\n    {\n      filename: path.join(FIXTURES, 'app/components/with-ts-class.hbs'),\n      code: '{{this.foo}}',\n    },\n    // Classic structure with a real .js companion class at app/components/<name>.js\n    {\n      filename: path.join(FIXTURES, 'app/templates/components/classic-with-class.hbs'),\n      code: '{{this.foo}}',\n    },\n    // Files outside any app/addon directory: cannot determine layout, don't flag\n    {\n      filename: '/some/random/path/foo.hbs',\n      code: '{{this.foo}}',\n    },\n  ],\n  invalid: [\n    // Template-only .hbs component (no companion file on disk — the path doesn't exist)\n    {\n      filename: 'app/components/nonexistent-test-component.hbs',\n      code: '{{this.foo}}',\n      output: '{{@foo}}',\n      errors: [{ messageId: 'noThis' }],\n    },\n    // Classic structure template-only component (no companion file on disk)\n    {\n      filename: 'app/templates/components/nonexistent-test-component.hbs',\n      code: '{{this.bar}}',\n      output: '{{@bar}}',\n      errors: [{ messageId: 'noThis' }],\n    },\n    // addon/components: same logic should apply\n    {\n      filename: 'addon/components/nonexistent-test-component.hbs',\n      code: '{{this.foo}}',\n      output: '{{@foo}}',\n      errors: [{ messageId: 'noThis' }],\n    },\n    // Built-in property (elementId) is not auto-fixable even when companion is missing\n    {\n      filename: 'app/components/nonexistent-test-component.hbs',\n      code: '{{this.elementId}}',\n      output: null,\n      errors: [{ messageId: 'noThis' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-trailing-spaces.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-trailing-spaces');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-trailing-spaces', rule, {\n  valid: [\n    `<template>\n      <div>Hello World</div>\n    </template>`,\n    `<template>\n      <div>\n        Content\n      </div>\n    </template>`,\n\n    '<template>test</template>',\n    '<template>   test</template>',\n    `<template>test\n</template>`,\n    `<template>{{#my-component}}\n{{/my-component}}</template>`,\n    `<template>  test\n</template>`,\n\n    // JS code with trailing spaces outside <template> must NOT be flagged\n    'const foo = \"bar\";  \\n\\n<template><div>Hello</div></template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template> \n      <div>Hello</div>\n    </template>`,\n      output: `<template>\n      <div>Hello</div>\n    </template>`,\n      errors: [\n        {\n          message: 'Trailing whitespace detected.',\n        },\n      ],\n    },\n    {\n      code: `<template>\n      <div>Hello</div>  \n    </template>`,\n      output: `<template>\n      <div>Hello</div>\n    </template>`,\n      errors: [\n        {\n          message: 'Trailing whitespace detected.',\n        },\n      ],\n    },\n\n    {\n      code: `<template>test \n</template>`,\n      output: `<template>test\n</template>`,\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: `<template>import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n  await render(hbs\\`  \n    <div class=\"parent\">\n      <div class=\"child\"></div>\n    </div>\n  \\`);\n});</template>`,\n      output: `<template>import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n  await render(hbs\\`\n    <div class=\"parent\">\n      <div class=\"child\"></div>\n    </div>\n  \\`);\n});</template>`,\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: `<template>import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n  await render(hbs\\`\n    <div></div>\n  \n    <div></div>\n  \\`);\n});</template>`,\n      output: `<template>import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n  await render(hbs\\`\n    <div></div>\n\n    <div></div>\n  \\`);\n});</template>`,\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: '<template>test\\n \\n</template>',\n      output: '<template>test\\n\\n</template>',\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: `<template>{{#my-component}}\n  test \n{{/my-component}}</template>`,\n      output: `<template>{{#my-component}}\n  test\n{{/my-component}}</template>`,\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-trailing-spaces', rule, {\n  valid: [\n    'test',\n    '   test',\n    'test\\n',\n    'test\\n\\n',\n    '{{#my-component}}\\n  test\\n{{/my-component}}',\n    `import { hbs } from 'ember-cli-htmlbars';\n\ntest('it renders', async (assert) => {\n  await render(hbs\\`\n    <div class=\"parent\">\n      <div class=\"child\"></div>\n    </div>\n  \\`);\n});`,\n  ],\n  invalid: [\n    {\n      code: 'test ',\n      output: 'test',\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: `test \n`,\n      output: `test\n`,\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: 'test\\n \\n',\n      output: 'test\\n\\n',\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: '{{#my-component}}\\n  test \\n{{/my-component}}',\n      output: '{{#my-component}}\\n  test\\n{{/my-component}}',\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: 'import { hbs } from \\'ember-cli-htmlbars\\';\\n\\ntest(\\'it renders\\', async (assert) => {\\n  await render(hbs`  \\n    <div class=\"parent\">\\n      <div class=\"child\"></div>\\n    </div>\\n  `);\\n});',\n      output:\n        'import { hbs } from \\'ember-cli-htmlbars\\';\\n\\ntest(\\'it renders\\', async (assert) => {\\n  await render(hbs`\\n    <div class=\"parent\">\\n      <div class=\"child\"></div>\\n    </div>\\n  `);\\n});',\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n    {\n      code: \"import { hbs } from 'ember-cli-htmlbars';\\n\\ntest('it renders', async (assert) => {\\n  await render(hbs`\\n    <div></div>\\n  \\n    <div></div>\\n  `);\\n});\",\n      output:\n        \"import { hbs } from 'ember-cli-htmlbars';\\n\\ntest('it renders', async (assert) => {\\n  await render(hbs`\\n    <div></div>\\n\\n    <div></div>\\n  `);\\n});\",\n      errors: [{ message: 'Trailing whitespace detected.' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-triple-curlies.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-triple-curlies');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{{foo}}',\n  '{{! template-lint-disable no-bare-strings }}',\n  '{{! template-lint-disable }}',\n  // Upstream also treats `{{! template-lint-disable no-triple-curlies}}{{{lol}}}` as valid,\n  // but this RuleTester does not honor template-lint disable directives.\n];\n\nconst invalidHbs = [\n  {\n    code: '\\n {{{foo}}}',\n    output: null,\n    errors: [{ message: 'Usage of triple curly brackets is unsafe' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n    errors: entry.errors.map(() => ({ messageId: 'unsafe' })),\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-triple-curlies', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-no-triple-curlies', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unavailable-this.js",
    "content": "const rule = require('../../../lib/rules/template-no-unavailable-this');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-unavailable-this', rule, {\n  valid: [\n    // this inside a class body\n    `import Component from '@glimmer/component';\n     class MyComponent extends Component {\n       <template>{{this.name}}</template>\n     }`,\n    // this inside a function\n    `function myComponent() {\n       return <template>{{this.name}}</template>;\n     }`,\n    // this inside an arrow function that is inside a class (arrow inherits this from class)\n    `import Component from '@glimmer/component';\n     class MyComponent extends Component {\n       foo = () => {\n         return <template>{{this.name}}</template>;\n       }\n     }`,\n    // No this at all\n    '<template>{{@value}}</template>',\n    '<template><div>Content</div></template>',\n    // yield this inside a class (this is valid, other rules handle yield specifics)\n    `import Component from '@glimmer/component';\n     class MyComponent extends Component {\n       <template>{{yield this}}</template>\n     }`,\n  ],\n  invalid: [\n    // this.property at module level\n    {\n      code: '<template>{{this.name}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnavailableThis' }],\n    },\n    // bare this at module level\n    {\n      code: '<template>{{this}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnavailableThis' }],\n    },\n    // yield this at module level (this rule catches the `this`, yield rule catches yield semantics)\n    {\n      code: '<template>{{yield this}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnavailableThis' }],\n    },\n    // multiple this references at module level\n    {\n      code: '<template>{{this.foo}} {{this.bar}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnavailableThis' }, { messageId: 'noUnavailableThis' }],\n    },\n    // deeply nested this.property at module level\n    {\n      code: '<template>{{this.foo.bar.baz}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnavailableThis' }],\n    },\n    // arrow function at module level (arrow functions don't have their own this)\n    {\n      code: `const myComponent = () => {\n       return <template>{{this}}</template>;\n     }`,\n      output: null,\n      errors: [{ messageId: 'noUnavailableThis' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unbalanced-curlies.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-unbalanced-curlies');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{foo}',\n  '{{foo}}',\n  '{{{foo}}}',\n  `{{{foo\n}}}`,\n  '\\\\{{foo}}',\n  '\\\\{{foo}}\\\\{{foo}}',\n  '\\\\{{foo}}{{foo}}',\n  `\\\\{{foo\n}}`,\n];\n\nconst invalidHbs = [\n  {\n    code: 'foo}}',\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: '{foo}}',\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: 'foo}}}',\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: '{foo}}}',\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: `{foo\n}}}`,\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: `{foo\n}}}\nbar`,\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: '{foo\\r\\nbar\\r\\n\\r\\nbaz}}}',\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n  {\n    code: '{foo\\rbar\\r\\rbaz}}}',\n    output: null,\n    errors: [{ message: 'Unbalanced curlies detected' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n    errors: entry.errors.map(() => ({ messageId: 'noUnbalancedCurlies' })),\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unbalanced-curlies', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unbalanced-curlies', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unbound.js",
    "content": "const rule = require('../../../lib/rules/template-no-unbound');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = ['{{foo}}', '{{button}}'];\n\nconst invalidHbs = [\n  {\n    code: '{{unbound foo}}',\n    output: null,\n    errors: [{ message: 'Unexpected {{unbound}} usage.' }],\n  },\n  {\n    code: '{{my-thing foo=(unbound foo)}}',\n    output: null,\n    errors: [{ message: 'Unexpected {{unbound}} usage.' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n    errors: entry.errors.map(() => ({ messageId: 'unexpected' })),\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unbound', rule, {\n  valid: [\n    ...validHbs.map(wrapTemplate),\n    // JS-scope shadowing: a user-imported `unbound` is not the Glimmer keyword.\n    {\n      filename: 'test.gjs',\n      code: \"import unbound from './my-unbound-helper';\\n<template>{{unbound foo}}</template>\",\n    },\n    {\n      filename: 'test.gts',\n      code: \"import unbound from '@some/addon';\\n<template>{{my-thing foo=(unbound foo)}}</template>\",\n    },\n    // Local block-param shadowing.\n    {\n      filename: 'test.gjs',\n      code: '<template>{{#let (component \"foo\") as |unbound|}}{{unbound}}{{/let}}</template>',\n    },\n  ],\n  invalid: [\n    ...invalidHbs.map(wrapTemplate),\n    // `unbound` is an ambient Glimmer keyword in strict mode — flag bare uses\n    // without a shadowing import or block param.\n    {\n      filename: 'test.gjs',\n      code: '<template>{{unbound foo}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n    {\n      filename: 'test.gts',\n      code: '<template>{{my-thing foo=(unbound foo)}}</template>',\n      output: null,\n      errors: [{ messageId: 'unexpected' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unbound', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unknown-arguments-for-builtin-components.js",
    "content": "const { RuleTester } = require('eslint');\nconst rule = require('../../../lib/rules/template-no-unknown-arguments-for-builtin-components');\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-no-unknown-arguments-for-builtin-components', rule, {\n  valid: [\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Input @type=\"text\" @value={{this.value}} />\n          </template>\n        }\n      `,\n      output: null,\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Textarea @value={{this.text}} />\n          </template>\n        }\n      `,\n    },\n\n    '<template><Input @value=\"foo\" /></template>',\n    '<template><Textarea @value=\"hello\" /></template>',\n    '<template><LinkTo @route=\"info\" @model={{this.model}} /></template>',\n    '<template><LinkTo @route=\"info\" /></template>',\n    '<template><LinkTo @query={{hash foo=bar}} /></template>',\n    '<template><LinkTo @model={{this.model}} /></template>',\n    '<template><LinkTo @models={{array comment.photo comment}} /></template>',\n\n    // Custom component imported with the same name as a built-in — should NOT be flagged\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Input from './my-custom-input';\n        <template><Input @unknownArg=\"x\" /></template>\n      `,\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Textarea from './my-custom-textarea';\n        <template><Textarea @customProp={{this.val}} /></template>\n      `,\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import LinkTo from './my-custom-link';\n        <template><LinkTo @anythingGoes=\"yes\" /></template>\n      `,\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Input from './my-custom-input';\n        export default class MyComponent {\n          <template><Input @customArg=\"value\" @anotherArg={{this.foo}} /></template>\n        }\n      `,\n    },\n  ],\n\n  invalid: [\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Input @unknownArg=\"value\" />\n          </template>\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'unknownArgument',\n        },\n      ],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <Textarea @invalidProp={{this.value}} />\n          </template>\n        }\n      `,\n      output: null,\n      errors: [\n        {\n          messageId: 'unknownArgument',\n        },\n      ],\n    },\n\n    {\n      code: '<template><Input @valuee={{this.content}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      code: '<template><Textarea @valuee={{this.content}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      code: '<template><LinkTo @route=\"foo\" @valuee={{this.content}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      code: '<template><LinkTo @route=\"foo\" @madel={{this.content}} /></template>',\n      output: null,\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      code: '<template><LinkTo @route=\"info\" @model={{this.model}} @models={{this.models}} /></template>',\n      output: null,\n      errors: [{ messageId: 'conflictArgument' }, { messageId: 'conflictArgument' }],\n    },\n    {\n      // Deprecated argument without a replacement attribute — autofixed by removal.\n      code: '<template><LinkTo @route=\"info\" @model={{this.model}} @tagName=\"button\" /></template>',\n      output: '<template><LinkTo @route=\"info\" @model={{this.model}} /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated argument with replacement — autofixed by renaming to the HTML attribute.\n      code: '<template><LinkTo @route=\"info\" @model={{this.model}} @elementId=\"superstar\" /></template>',\n      output: '<template><LinkTo @route=\"info\" @model={{this.model}} id=\"superstar\" /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated event with a helper invocation value — migrated to an `{{on}}` modifier with the helper as a sub-expression.\n      code: '<template><LinkTo @route=\"info\" @model={{this.model}} @doubleClick={{action this.click}} /></template>',\n      output:\n        '<template><LinkTo @route=\"info\" @model={{this.model}} {{on \"dblclick\" (action this.click)}} /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated argument without a replacement attribute — autofixed by removal.\n      code: '<template><Input @value=\"1\" @bubbles={{false}} /></template>',\n      output: '<template><Input @value=\"1\" /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Two deprecated arguments on Input — both renamed to HTML attributes.\n      code: '<template><Input @value=\"1\" @elementId=\"42\" @disabled=\"disabled\" /></template>',\n      output: '<template><Input @value=\"1\" id=\"42\" disabled=\"disabled\" /></template>',\n      errors: [{ messageId: 'unknownArgument' }, { messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated event with a simple path value — migrated to an `{{on}}` modifier.\n      code: '<template><Input @value=\"1\" @key-up={{ths.onKeyUp}} /></template>',\n      output: '<template><Input @value=\"1\" {{on \"keyup\" ths.onKeyUp}} /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated argument without a replacement attribute — autofixed by removal.\n      code: '<template><Textarea @value=\"1\" @bubbles={{false}} /></template>',\n      output: '<template><Textarea @value=\"1\" /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated argument with replacement — autofixed by renaming to the HTML attribute.\n      code: '<template><Textarea @value=\"1\" @elementId=\"42\" /></template>',\n      output: '<template><Textarea @value=\"1\" id=\"42\" /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated event with a simple path value — migrated to an `{{on}}` modifier.\n      code: '<template><Textarea @value=\"1\" @key-up={{ths.onKeyUp}} /></template>',\n      output: '<template><Textarea @value=\"1\" {{on \"keyup\" ths.onKeyUp}} /></template>',\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Truly unknown/typo argument — not autofixed.\n      code: '<template> <LinkTo class=\"auk-search-results-list__item\" @route={{@route}} @models={{this.models}} @random=\"test\" @query={{@query}} ...attributes >Hello</LinkTo></template>',\n      output: null,\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n    {\n      // Deprecated event with a string-literal value — cannot migrate to `{{on}}`, so no autofix.\n      code: '<template><Input @value=\"1\" @click=\"noop\" /></template>',\n      output: null,\n      errors: [{ messageId: 'unknownArgument' }],\n    },\n\n    // Missing required arguments\n    {\n      code: '<template><LinkTo /></template>',\n      output: null,\n      errors: [{ messageId: 'requiredArgument' }],\n    },\n    {\n      code: '<template><LinkTo @disabled={{true}} /></template>',\n      output: null,\n      errors: [{ messageId: 'requiredArgument' }],\n    },\n    {\n      filename: 'my-component.gjs',\n      code: `\n        import Component from '@glimmer/component';\n        export default class MyComponent extends Component {\n          <template>\n            <LinkTo @replace={{true}}>Home</LinkTo>\n          </template>\n        }\n      `,\n      output: null,\n      errors: [{ messageId: 'requiredArgument' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unknown-arguments-for-builtin-components', rule, {\n  valid: [\n    '<Input @value=\"foo\" />',\n    '<Textarea @value=\"hello\" />',\n    '<LinkTo @route=\"info\" @model={{this.model}} />',\n    '<LinkTo @route=\"info\" />',\n    '<LinkTo @query={{hash foo=bar}} />',\n    '<LinkTo @model={{this.model}} />',\n    '<LinkTo @models={{array comment.photo comment}} />',\n  ],\n  invalid: [\n    {\n      code: '<Input @valuee={{this.content}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '\"@valuee\" is not a known argument for the <Input /> component. Did you mean \"@value\"?',\n        },\n      ],\n    },\n    {\n      code: '<Textarea @valuee={{this.content}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '\"@valuee\" is not a known argument for the <Textarea /> component. Did you mean \"@value\"?',\n        },\n      ],\n    },\n    {\n      code: '<LinkTo @route=\"foo\" @valuee={{this.content}} />',\n      output: null,\n      errors: [{ message: '\"@valuee\" is not a known argument for the <LinkTo /> component.' }],\n    },\n    {\n      code: '<LinkTo @route=\"foo\" @madel={{this.content}} />',\n      output: null,\n      errors: [{ message: '\"@madel\" is not a known argument for the <LinkTo /> component.' }],\n    },\n    {\n      code: '<LinkTo @route=\"info\" @model={{this.model}} @models={{this.models}} />',\n      output: null,\n      errors: [\n        { message: '\"@model\" conflicts with \"@models\", only one should exist.' },\n        { message: '\"@models\" conflicts with \"@model\", only one should exist.' },\n      ],\n    },\n    {\n      code: '<LinkTo @route=\"info\" @model={{this.model}} @tagName=\"button\" />',\n      output: '<LinkTo @route=\"info\" @model={{this.model}} />',\n      errors: [{ message: 'Passing the \"@tagName\" argument to <LinkTo /> is deprecated.' }],\n    },\n    {\n      code: '<LinkTo @route=\"info\" @model={{this.model}} @elementId=\"superstar\" />',\n      output: '<LinkTo @route=\"info\" @model={{this.model}} id=\"superstar\" />',\n      errors: [\n        {\n          message: `Passing the \"@elementId\" argument to <LinkTo /> is deprecated.\nInstead, please pass the attribute directly, i.e. \"<LinkTo id={{...}} />\" instead of \"<LinkTo @elementId={{...}} />\".`,\n        },\n      ],\n    },\n    {\n      code: '<LinkTo @route=\"info\" @model={{this.model}} @doubleClick={{action this.click}} />',\n      output:\n        '<LinkTo @route=\"info\" @model={{this.model}} {{on \"dblclick\" (action this.click)}} />',\n      errors: [\n        {\n          message: `Passing the \"@doubleClick\" argument to <LinkTo /> is deprecated.\nInstead, please use the {{on}} modifier, i.e. \"<LinkTo {{on \"dblclick\" ...}} />\" instead of \"<LinkTo @doubleClick={{...}} />\".`,\n        },\n      ],\n    },\n    {\n      code: '<Input @value=\"1\" @bubbles={{false}} />',\n      output: '<Input @value=\"1\" />',\n      errors: [{ message: 'Passing the \"@bubbles\" argument to <Input /> is deprecated.' }],\n    },\n    {\n      code: '<Input @value=\"1\" @elementId=\"42\" @disabled=\"disabled\" />',\n      output: '<Input @value=\"1\" id=\"42\" disabled=\"disabled\" />',\n      errors: [\n        {\n          message: `Passing the \"@elementId\" argument to <Input /> is deprecated.\nInstead, please pass the attribute directly, i.e. \"<Input id={{...}} />\" instead of \"<Input @elementId={{...}} />\".`,\n        },\n        {\n          message: `Passing the \"@disabled\" argument to <Input /> is deprecated.\nInstead, please pass the attribute directly, i.e. \"<Input disabled={{...}} />\" instead of \"<Input @disabled={{...}} />\".`,\n        },\n      ],\n    },\n    {\n      code: '<Input @value=\"1\" @key-up={{ths.onKeyUp}} />',\n      output: '<Input @value=\"1\" {{on \"keyup\" ths.onKeyUp}} />',\n      errors: [\n        {\n          message: `Passing the \"@key-up\" argument to <Input /> is deprecated.\nInstead, please use the {{on}} modifier, i.e. \"<Input {{on \"keyup\" ...}} />\" instead of \"<Input @key-up={{...}} />\".`,\n        },\n      ],\n    },\n    {\n      code: '<Textarea @value=\"1\" @bubbles={{false}} />',\n      output: '<Textarea @value=\"1\" />',\n      errors: [{ message: 'Passing the \"@bubbles\" argument to <Textarea /> is deprecated.' }],\n    },\n    {\n      code: '<Textarea @value=\"1\" @elementId=\"42\" />',\n      output: '<Textarea @value=\"1\" id=\"42\" />',\n      errors: [\n        {\n          message: `Passing the \"@elementId\" argument to <Textarea /> is deprecated.\nInstead, please pass the attribute directly, i.e. \"<Textarea id={{...}} />\" instead of \"<Textarea @elementId={{...}} />\".`,\n        },\n      ],\n    },\n    {\n      code: '<Textarea @value=\"1\" @key-up={{ths.onKeyUp}} />',\n      output: '<Textarea @value=\"1\" {{on \"keyup\" ths.onKeyUp}} />',\n      errors: [\n        {\n          message: `Passing the \"@key-up\" argument to <Textarea /> is deprecated.\nInstead, please use the {{on}} modifier, i.e. \"<Textarea {{on \"keyup\" ...}} />\" instead of \"<Textarea @key-up={{...}} />\".`,\n        },\n      ],\n    },\n    {\n      code: ' <LinkTo class=\"auk-search-results-list__item\" @route={{@route}} @models={{this.models}} @random=\"test\" @query={{@query}} ...attributes >Hello</LinkTo>',\n      output: null,\n      errors: [\n        {\n          message:\n            '\"@random\" is not a known argument for the <LinkTo /> component. Did you mean \"@dragEnd\"?',\n        },\n      ],\n    },\n\n    // Missing required arguments\n    {\n      code: '<LinkTo />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Arguments \"@route\" or \"@query\" or \"@model\" or \"@models\" is required for <LinkTo /> component.',\n        },\n      ],\n    },\n    {\n      code: '<LinkTo @disabled={{true}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Arguments \"@route\" or \"@query\" or \"@model\" or \"@models\" is required for <LinkTo /> component.',\n        },\n      ],\n    },\n    {\n      code: '<LinkTo @replace={{true}}>Home</LinkTo>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Arguments \"@route\" or \"@query\" or \"@model\" or \"@models\" is required for <LinkTo /> component.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unnecessary-component-helper.js",
    "content": "const eslint = require('eslint');\nconst rule = require('../../../lib/rules/template-no-unnecessary-component-helper');\n\nconst { RuleTester } = eslint;\n\nconst validHbs = [\n  '{{component SOME_COMPONENT_NAME}}',\n  '{{component SOME_COMPONENT_NAME SOME_ARG}}',\n  '{{component SOME_COMPONENT_NAME \"Hello World\"}}',\n  '{{my-component}}',\n  '{{my-component \"Hello world\"}}',\n  '{{my-component \"Hello world\" 123}}',\n  '{{#component SOME_COMPONENT_NAME}}{{/component}}',\n  '{{#component SOME_COMPONENT_NAME SOME_ARG}}{{/component}}',\n  '{{#component SOME_COMPONENT_NAME \"Hello World\"}}{{/component}}',\n  '{{#my-component}}{{/my-component}}',\n  '{{#my-component \"Hello world\"}}{{/my-component}}',\n  '{{#my-component \"Hello world\" 123}}{{/my-component}}',\n  '(component SOME_COMPONENT_NAME)',\n  '(component \"my-component\")',\n  '<Foo @bar={{component SOME_COMPONENT_NAME}} />',\n  '<Foo @bar={{component \"my-component\"}} />',\n  '<Foo @bar={{component SOME_COMPONENT_NAME}}></Foo>',\n  '<Foo @bar={{component \"my-component\"}}></Foo>',\n  '<Foo @arg=\"foo\" />',\n  '<Foo class=\"foo\" />',\n  '<Foo data-test-bar=\"foo\" />',\n  '<Foo @arg={{if this.user.isAdmin \"admin\"}} />',\n  '<Foo @arg={{if this.user.isAdmin (component \"my-component\")}} />',\n  \"{{component 'addon-name@component-name'}}\",\n  \"{{#component 'addon-name@component-name'}}{{/component}}\",\n];\n\nconst invalidHbs = [\n  {\n    code: '{{component \"my-component-name\" foo=123 bar=456}}',\n    output: '{{my-component-name foo=123 bar=456}}',\n    errors: [{ message: 'Invoke component directly instead of using `component` helper' }],\n  },\n  {\n    code: '{{#component \"my-component-name\" foo=123 bar=456}}{{/component}}',\n    output: '{{#my-component-name foo=123 bar=456}}{{/my-component-name}}',\n    errors: [{ message: 'Invoke component directly instead of using `component` helper' }],\n  },\n  {\n    code: '<Foo @arg={{component \"allowed-component\"}}>{{component \"forbidden-component\"}}</Foo>',\n    output: '<Foo @arg={{component \"allowed-component\"}}>{{forbidden-component}}</Foo>',\n    errors: [{ message: 'Invoke component directly instead of using `component` helper' }],\n  },\n];\n\nconst validGjs = [\n  '<template>{{component SOME_COMPONENT_NAME}}</template>',\n  '<template>{{component SOME_COMPONENT_NAME SOME_ARG}}</template>',\n  '<template>{{component SOME_COMPONENT_NAME \"Hello World\"}}</template>',\n  '<template>{{my-component}}</template>',\n  '<template>{{my-component \"Hello world\"}}</template>',\n  '<template>{{my-component \"Hello world\" 123}}</template>',\n  '<template>{{#component SOME_COMPONENT_NAME}}{{/component}}</template>',\n  '<template>{{#component SOME_COMPONENT_NAME SOME_ARG}}{{/component}}</template>',\n  '<template>{{#component SOME_COMPONENT_NAME \"Hello World\"}}{{/component}}</template>',\n  '<template>{{#my-component}}{{/my-component}}</template>',\n  '<template>{{#my-component \"Hello world\"}}{{/my-component}}</template>',\n  '<template>{{#my-component \"Hello world\" 123}}{{/my-component}}</template>',\n  '<template><Foo @bar={{component SOME_COMPONENT_NAME}} /></template>',\n  '<template><Foo @bar={{component \"my-component\"}} /></template>',\n  '<template><Foo @bar={{component SOME_COMPONENT_NAME}}></Foo></template>',\n  '<template><Foo @bar={{component \"my-component\"}}></Foo></template>',\n  '<template><Foo @arg=\"foo\" /></template>',\n  '<template><Foo class=\"foo\" /></template>',\n  '<template><Foo data-test-bar=\"foo\" /></template>',\n  '<template><Foo @arg={{if this.user.isAdmin \"admin\"}} /></template>',\n  '<template><Foo @arg={{if this.user.isAdmin (component \"my-component\")}} /></template>',\n  \"<template>{{component 'addon-name@component-name'}}</template>\",\n  \"<template>{{#component 'addon-name@component-name'}}{{/component}}</template>\",\n];\n\nfunction wrapTemplate(entry) {\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n    errors: entry.errors.map(() => ({ messageId: 'noUnnecessaryComponent' })),\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unnecessary-component-helper', rule, {\n  valid: validGjs,\n  invalid: [\n    ...invalidHbs.map(wrapTemplate),\n    // GJS/GTS: kebab-case names can't be valid JS identifiers — report with\n    // a dedicated message suggesting the PascalCase form and import.\n    // Full migration (including adding the import) is best handled by\n    // ember-codemods/angle-brackets-codemod.\n    {\n      filename: 'test.gjs',\n      code: '<template>{{component \"my-component-name\" foo=123}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnnecessaryComponentKebab' }],\n    },\n    {\n      filename: 'test.gts',\n      code: '<template>{{#component \"my-component-name\"}}content{{/component}}</template>',\n      output: null,\n      errors: [{ messageId: 'noUnnecessaryComponentKebab' }],\n    },\n    // GJS/GTS: valid JS identifier → autofix still applies\n    {\n      filename: 'test.gjs',\n      code: '<template>{{component \"myComponent\" foo=123}}</template>',\n      output: '<template>{{myComponent foo=123}}</template>',\n      errors: [{ messageId: 'noUnnecessaryComponent' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unnecessary-component-helper', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unnecessary-concat.js",
    "content": "const rule = require('../../../lib/rules/template-no-unnecessary-concat');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '<div class={{clazz}}></div>',\n  '<div class=\"first {{second}}\"></div>',\n  '\"{{foo}}\"',\n];\n\nconst invalidHbs = [\n  {\n    code: '<div class=\"{{clazz}}\"></div>',\n    output: '<div class={{clazz}}></div>',\n    errors: [\n      { message: 'Unnecessary string concatenation. Use {{clazz}} instead of \"{{clazz}}\".' },\n    ],\n  },\n  {\n    code: '<img src=\"{{url}}\" alt=\"{{t \"alternate-text\"}}\">',\n    output: '<img src={{url}} alt={{t \"alternate-text\"}}>',\n    errors: [\n      { message: 'Unnecessary string concatenation. Use {{url}} instead of \"{{url}}\".' },\n      {\n        message:\n          'Unnecessary string concatenation. Use {{t \"alternate-text\"}} instead of \"{{t \"alternate-text\"}}\".',\n      },\n    ],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n    errors: entry.errors.map(() => ({ messageId: 'unnecessary' })),\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unnecessary-concat', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unnecessary-concat', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unnecessary-curly-parens.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-unnecessary-curly-parens');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{{foo}}',\n  '{{this.foo}}',\n  '{{(helper)}}',\n  '{{(this.helper)}}',\n  '{{concat \"a\" \"b\"}}',\n  '{{concat (capitalize \"foo\") \"-bar\"}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '<FooBar @x=\"{{index}}X{{(someHelper foo)}}\" />',\n    output: '<FooBar @x=\"{{index}}X{{someHelper foo}}\" />',\n    errors: [{ messageId: 'noUnnecessaryCurlyParens' }],\n  },\n  {\n    code: '<FooBar @x=\"{{index}}XY{{(someHelper foo)}}\" />',\n    output: '<FooBar @x=\"{{index}}XY{{someHelper foo}}\" />',\n    errors: [{ messageId: 'noUnnecessaryCurlyParens' }],\n  },\n  {\n    code: '<FooBar @x=\"{{index}}--{{(someHelper foo)}}\" />',\n    output: '<FooBar @x=\"{{index}}--{{someHelper foo}}\" />',\n    errors: [{ messageId: 'noUnnecessaryCurlyParens' }],\n  },\n  {\n    code: '{{(concat \"a\" \"b\")}}',\n    output: '{{concat \"a\" \"b\"}}',\n    errors: [{ messageId: 'noUnnecessaryCurlyParens' }],\n  },\n  {\n    code: '{{(helper a=\"b\")}}',\n    output: '{{helper a=\"b\"}}',\n    errors: [{ messageId: 'noUnnecessaryCurlyParens' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unnecessary-curly-parens', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unnecessary-curly-parens', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unnecessary-curly-strings.js",
    "content": "const rule = require('../../../lib/rules/template-no-unnecessary-curly-strings');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '<FooBar class=\"btn\" />',\n  '{{foo}}',\n  '{{(foo)}}',\n  '{{this.calculate 1 2 op=\"add\"}}',\n  '{{get address part}}',\n  'foo',\n  '\"foo\"',\n  '<FooBar value=12345 />',\n  '<FooBar value=null />',\n  '<FooBar value=true />',\n  '<FooBar value=undefined />',\n  '<FooBar value={{12345}} />',\n  '<FooBar value={{null}} />',\n  '<FooBar value={{true}} />',\n  '<FooBar value={{undefined}} />',\n];\n\nconst invalidHbs = [\n  {\n    code: '<FooBar class={{\"btn\"}} @fooArg={{\\'barbaz\\'}} />',\n    output: '<FooBar class=\"btn\" @fooArg=\\'barbaz\\' />',\n    errors: [{ messageId: 'unnecessary' }, { messageId: 'unnecessary' }],\n  },\n  {\n    code: '<FooBar class=\"btn\">{{\"Foo\"}}</FooBar>',\n    output: '<FooBar class=\"btn\">Foo</FooBar>',\n    errors: [{ messageId: 'unnecessary' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unnecessary-curly-strings', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unnecessary-curly-strings', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unsupported-role-attributes.js",
    "content": "const rule = require('../../../lib/rules/template-no-unsupported-role-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '<div role=\"button\" aria-disabled=\"true\"></div>',\n  '<div role=\"heading\" aria-level=\"1\" />',\n  '<span role=\"checkbox\" aria-checked={{this.checked}}></span>',\n  '<CustomComponent role=\"banner\" />',\n  '<div role=\"textbox\" aria-required={{this.required}} aria-errormessage={{this.error}}></div>',\n  '<div role=\"heading\" foo=\"true\" />',\n  '<dialog />',\n  '<a href=\"#\" aria-describedby=\"\"></a>',\n  '<menu type=\"toolbar\" aria-hidden=\"true\" />',\n  '<a role=\"menuitem\" aria-labelledby={{this.label}} />',\n  '<input type=\"image\" aria-atomic />',\n  '<input type=\"submit\" aria-disabled=\"true\" />',\n  '<select aria-expanded=\"false\" aria-controls=\"ctrlID\" />',\n  '<div type=\"button\" foo=\"true\" />',\n  '{{some-component role=\"heading\" aria-level=\"2\"}}',\n  '{{other-component role=this.role aria-bogus=\"true\"}}',\n  '<ItemCheckbox @model={{@model}} @checkable={{@checkable}} />',\n  '<some-custom-element />',\n  '<input type=\"password\">',\n\n  // <input type=\"password\"> has no implicit role per aria-query (it's intentionally\n  // not mapped so that screen readers don't announce typed content). No role →\n  // no aria-supported-props check. Pin this with attributes that would be REJECTED\n  // on a textbox (aria-checked, aria-selected): if the rule mis-classified password\n  // as a textbox fallback, these would flag.\n  '<input type=\"password\" aria-describedby=\"hint\" />',\n  '<input type=\"password\" aria-required=\"true\" />',\n  '<input type=\"password\" aria-checked=\"false\" />',\n  '<input type=\"password\" aria-selected=\"true\" />',\n\n  // <input type=\"text\"> without a list attribute is a textbox — aria-required,\n  // aria-readonly, aria-placeholder are all supported.\n  '<input type=\"text\" aria-required=\"true\" />',\n  '<input type=\"email\" aria-readonly=\"true\" />',\n  '<input type=\"tel\" aria-required=\"true\" />',\n  '<input type=\"url\" aria-placeholder=\"https://…\" />',\n];\n\nconst invalidHbs = [\n  {\n    code: '<div role=\"link\" href=\"#\" aria-checked />',\n    output: '<div role=\"link\" href=\"#\" />',\n    errors: [{ message: 'The attribute aria-checked is not supported by the role link' }],\n  },\n  {\n    code: '<CustomComponent role=\"listbox\" aria-level=\"2\" />',\n    output: '<CustomComponent role=\"listbox\" />',\n    errors: [{ message: 'The attribute aria-level is not supported by the role listbox' }],\n  },\n  {\n    code: '<div role=\"option\" aria-notreal=\"bogus\" aria-selected=\"false\" />',\n    output: '<div role=\"option\" aria-selected=\"false\" />',\n    errors: [{ message: 'The attribute aria-notreal is not supported by the role option' }],\n  },\n  {\n    code: '<div role=\"combobox\" aria-multiline=\"true\" aria-expanded=\"false\" aria-controls=\"someId\" />',\n    output: '<div role=\"combobox\" aria-expanded=\"false\" aria-controls=\"someId\" />',\n    errors: [{ message: 'The attribute aria-multiline is not supported by the role combobox' }],\n  },\n  {\n    code: '<button type=\"submit\" aria-valuetext=\"woosh\"></button>',\n    output: '<button type=\"submit\"></button>',\n    errors: [\n      {\n        message:\n          'The attribute aria-valuetext is not supported by the element button with the implicit role of button',\n      },\n    ],\n  },\n  {\n    code: '<menu type=\"toolbar\" aria-expanded=\"true\" />',\n    output: '<menu type=\"toolbar\" />',\n    errors: [\n      {\n        message:\n          'The attribute aria-expanded is not supported by the element menu with the implicit role of list',\n      },\n    ],\n  },\n  {\n    code: '<a role=\"menuitem\" aria-checked={{this.checked}} />',\n    output: '<a role=\"menuitem\" />',\n    errors: [{ message: 'The attribute aria-checked is not supported by the role menuitem' }],\n  },\n  {\n    code: '<input type=\"button\" aria-invalid=\"grammar\" />',\n    output: '<input type=\"button\" />',\n    errors: [\n      {\n        message:\n          'The attribute aria-invalid is not supported by the element input with the implicit role of button',\n      },\n    ],\n  },\n  {\n    // <input type=\"email\"> without a `list` attribute → implicit role \"textbox\"\n    // (per aria-query / HTML-AAM). With a `list` attribute it would be \"combobox\".\n    code: '<input type=\"email\" aria-level={{this.level}} />',\n    output: '<input type=\"email\" />',\n    errors: [\n      {\n        message:\n          'The attribute aria-level is not supported by the element input with the implicit role of textbox',\n      },\n    ],\n  },\n  {\n    // With a `list` attribute, <input type=\"email\"> becomes a combobox.\n    code: '<input type=\"email\" list=\"x\" aria-level={{this.level}} />',\n    output: '<input type=\"email\" list=\"x\" />',\n    errors: [\n      {\n        message:\n          'The attribute aria-level is not supported by the element input with the implicit role of combobox',\n      },\n    ],\n  },\n  {\n    code: '{{foo-component role=\"button\" aria-valuetext=\"blahblahblah\"}}',\n    output: '{{foo-component role=\"button\"}}',\n    errors: [{ message: 'The attribute aria-valuetext is not supported by the role button' }],\n  },\n  // Documented divergence with jsx-a11y on implicit role for <body>.\n  // jsx-a11y resolves <body> to role \"document\"; aria-query (which our rule\n  // uses) resolves to \"generic\". aria-expanded is unsupported by either, so\n  // both plugins flag — only the role-name in the message differs.\n  {\n    code: '<body aria-expanded=\"true\"></body>',\n    output: '<body></body>',\n    errors: [\n      {\n        message:\n          'The attribute aria-expanded is not supported by the element body with the implicit role of generic',\n      },\n    ],\n  },\n  // <a> without href — implicit role is `generic` per HTML-AAM 1.2 §3.5.3\n  // (https://www.w3.org/TR/html-aam/#el-a-no-href). aria-checked is not\n  // supported on `generic`, so we flag. vue-a11y reaches the same conclusion\n  // (it walks aria-query the same way). jsx-a11y's `getImplicitRoleForAnchor`\n  // returns `''` for href-less <a>, which makes its role-supports-aria-props\n  // rule early-return and silently allow any aria-* attribute — its own\n  // source comments this as \"This actually isn't true - should fix in future\n  // release.\" We're spec-current; jsx-a11y is spec-stale (legacy ARIA 1.1\n  // mental model).\n  {\n    code: '<a aria-checked />',\n    output: '<a />',\n    errors: [{ messageId: 'unsupportedImplicit' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unsupported-role-attributes', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unsupported-role-attributes', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-unused-block-params.js",
    "content": "const eslint = require('eslint');\nconst rule = require('../../../lib/rules/template-no-unused-block-params');\n\nconst { RuleTester } = eslint;\n\nconst validHbs = [\n  '{{cat}}',\n  '{{#each cats as |cat|}}{{cat}}{{/each}}',\n  '{{#each cats as |cat|}}{{partial \"cat\"}}{{/each}}',\n  '{{#each cats as |cat|}}{{cat.name}}{{/each}}',\n  '{{#each cats as |cat|}}{{meow cat}}{{/each}}',\n  '{{#each cats as |cat index|}}{{index}}{{/each}}',\n  '{{#each cats as |cat index|}}{{#each cat.lives as |life|}}{{index}}: {{life}}{{/each}}{{/each}}',\n  `\n  {{#each cats as |cat|}}\n    <div data-cat-name={{cat.name}}></div>\n  {{/each}}\n  `,\n  `\n  {{#each cats as |cat|}}\n    <div class=\"cat {{cat.name}}\"></div>\n  {{/each}}\n  `,\n  `\n  {{#each cats as |cat|}}\n    <div class=\"cat {{if (isFavorite cat.name favoriteCatNames) 'favorite'}}\"></div>\n  {{/each}}\n  `,\n  `\n    <MyComponent @model={{this.model}} as |param|>\n      {{! template-lint-disable }}\n        <MyOtherComponent .... @param={{param}} />\n      {{! template-lint-enable }}\n    </MyComponent>\n    `,\n  `\n    <MyComponent @model={{this.model}} as |param|>\n      {{! template-lint-disable }}\n        {{foo-bar param}}\n      {{! template-lint-enable }}\n    </MyComponent>\n    `,\n  `\n    <MyComponent @model={{this.model}} as |param|>\n      {{! template-lint-disable }}\n        {{param}}\n      {{! template-lint-enable }}\n    </MyComponent>\n    `,\n  `\n    <MyComponent @model={{this.model}} as |param|>\n      {{! template-lint-disable }}\n        {{foo-bar prop=param}}\n      {{! template-lint-enable }}\n    </MyComponent>\n    `,\n  `\n    {{#my-component as |param|}}\n      {{! template-lint-disable }}\n        <MyOtherComponent .... @param={{param}} />\n      {{! template-lint-enable }}\n    {{/my-component}}\n    `,\n  `\n    {{#my-component as |param|}}\n      {{! template-lint-disable }}\n        {{foo-bar param}}\n      {{! template-lint-enable }}\n    {{/my-component}}\n    `,\n  `\n    {{#my-component as |param|}}\n      {{! template-lint-disable }}\n        {{param}}\n      {{! template-lint-enable }}\n    {{/my-component}}\n    `,\n  `\n    {{#my-component as |param bar baz|}}\n      {{! template-lint-disable }}\n        {{foo-bar prop=param}}\n      {{! template-lint-enable }}\n      {{bar}}\n      {{! template-lint-disable }}\n        {{foo-bar prop=baz}}\n      {{! template-lint-enable }}\n    {{/my-component}}\n    `,\n  '{{#each cats as |cat|}}{{#meow-meow cat as |cat|}}{{cat}}{{/meow-meow}}{{/each}}',\n  '{{#with (component \"foo-bar\") as |FooBar|}}<FooBar />{{/with}}',\n  '<BurgerMenu as |menu|><header>Something</header><menu.item>Text</menu.item></BurgerMenu>',\n  '{{#burger-menu as |menu|}}<header>Something</header>{{#menu.item}}Text{{/menu.item}}{{/burger-menu}}',\n  '<MyComponent as |item|>{{item}}</MyComponent>',\n  // Outer `item` is used via @value (outer scope); inner `as |item|` shadows\n  // outer in <Inner>'s children.\n  '<Outer as |item|><Inner @value={{item}} as |item|>{{item}}</Inner></Outer>',\n  '<MyComponent as |handler|><button {{on \"click\" handler}}>X</button></MyComponent>',\n  '{{#let this.fn as |handler|}}<button {{on \"click\" handler}}>X</button>{{/let}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{#each cats as |cat|}}Dogs{{/each}}',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'cat' } }],\n  },\n  {\n    code: '{{#each cats as |cat index|}}{{cat}}{{/each}}',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'index' } }],\n  },\n  {\n    code: '{{#each cats as |cat index|}}{{#each cat.lives as |life index|}}{{index}}: {{life}}{{/each}}{{/each}}',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'index' } }],\n  },\n  {\n    code: '{{#each cats as |cat index|}}{{partial \"cat\"}}{{#each cat.lives as |life|}}Life{{/each}}{{/each}}',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'life' } }],\n  },\n  {\n    code: '{{#each cats as |cat|}}plain cat text{{/each}}',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'cat' } }],\n  },\n  {\n    code: '{{#each cats as |cat|}}{{a.different.cat}}{{/each}}',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'cat' } }],\n  },\n  {\n    code: '<MyComponent as |item unused|>{{item}}</MyComponent>',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'unused' } }],\n  },\n  {\n    code: '<MyComponent as |unused|>content</MyComponent>',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'unused' } }],\n  },\n  // The outer `item` is only \"used\" inside <Inner> where it is shadowed by\n  // the inner `as |item|`, so the outer should be reported as unused.\n  {\n    code: '<Outer as |item|><Inner as |item|>{{item}}</Inner></Outer>',\n    output: null,\n    errors: [{ messageId: 'unusedBlockParam', data: { param: 'item' } }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-unused-block-params', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-unused-block-params', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-valueless-arguments.js",
    "content": "const rule = require('../../../lib/rules/template-no-valueless-arguments');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '<SomeComponent @emptyString=\"\" data-test-some-component />',\n  '<button type=\"submit\" disabled {{on \"click\" this.submit}}></button>',\n];\n\nconst invalidHbs = [\n  {\n    code: '<SomeComponent @valueless />',\n    output: null,\n    errors: [{ messageId: 'valueless' }],\n  },\n  {\n    code: '<SomeComponent @valuelessByAccident{{this.canBeAModifier}} />',\n    output: null,\n    errors: [{ messageId: 'valueless' }],\n  },\n  {\n    code: '<SomeComponent @valuelessByAccident{{@canBeAModifier}} />',\n    output: null,\n    errors: [{ messageId: 'valueless' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-valueless-arguments', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-valueless-arguments', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-whitespace-for-layout.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-no-whitespace-for-layout');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  'Start to finish',\n  'Start&nbsp;to&nbsp;finish',\n  'Start<br>to<br>finish',\n  `<div>\n  example\n</div>`,\n  `<div\n  foo=\"bar\"\n  baz=\"qux\"\n>\n  example\n</div>`,\n  // Attribute values with consecutive spaces must not be flagged (false positive,\n  // cf. ember-template-lint#2899) — the rule targets element body text only.\n  '<div class=\"foo  bar\"></div>',\n  '<div style=\"margin: 0;  padding: 0\"></div>',\n];\n\nconst invalidHbs = [\n  {\n    code: 'START  FINISH',\n    output: null,\n    errors: [{ message: 'Excess whitespace detected.' }],\n  },\n  {\n    code: 'START&nbsp;&nbsp;FINISH',\n    output: null,\n    errors: [{ message: 'Excess whitespace detected.' }],\n  },\n  {\n    code: 'START&nbsp; FINISH',\n    output: null,\n    errors: [{ message: 'Excess whitespace detected.' }],\n  },\n  {\n    code: 'START &nbsp;FINISH',\n    output: null,\n    errors: [{ message: 'Excess whitespace detected.' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-whitespace-for-layout', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-whitespace-for-layout', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-whitespace-within-word.js",
    "content": "const eslint = require('eslint');\nconst rule = require('../../../lib/rules/template-no-whitespace-within-word');\n\nconst { RuleTester } = eslint;\n\nconst validHbs = [\n  'Welcome',\n  'Hey - I like this!',\n  'Expected: 5-10 guests',\n  'Expected: 5 - 10 guests',\n  'It is possible to get some examples of in-word emph a sis past this rule.',\n  'However, I do not want a rule that flags annoying false positives for correctly-used single-character words.',\n  '<div>Welcome</div>',\n  '<div enable-background=\"a b c d e f g h i j k l m\">We want to ignore values of HTML attributes</div>',\n  `<style>\n  .my-custom-class > * {\n    border: 2px dotted red;\n  }\n</style>`,\n];\n\nconst invalidHbs = [\n  {\n    code: 'W e l c o m e',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n  {\n    code: 'W&nbsp;e&nbsp;l&nbsp;c&nbsp;o&nbsp;m&nbsp;e',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n  {\n    code: 'Wel c o me',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n  {\n    code: 'Wel&nbsp;c&emsp;o&nbsp;me',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n  {\n    code: '<div>W e l c o m e</div>',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n  {\n    code: '<div>Wel c o me</div>',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n  {\n    code: 'A  B&nbsp;&nbsp; C ',\n    output: null,\n    errors: [{ message: 'Excess whitespace in layout detected.' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-whitespace-within-word', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-whitespace-within-word', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-with.js",
    "content": "const rule = require('../../../lib/rules/template-no-with');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{{@with}}',\n  '{{this.with}}',\n  '{{with \"foo\" bar=\"baz\"}}',\n  '{{#if @model.posts}}{{@model.posts}}{{/if}}',\n  '{{#let @model.posts as |blogPosts|}}{{blogPosts}}{{/let}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{#with this.foo as |bar|}}{{bar}}{{/with}}',\n    output: null,\n    errors: [{ messageId: 'deprecated' }],\n  },\n  {\n    code: '{{#with (hash firstName=\"John\" lastName=\"Doe\") as |user|}}{{user.firstName}} {{user.lastName}}{{/with}}',\n    output: null,\n    errors: [{ messageId: 'deprecated' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-with', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-with', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-yield-block-params-to-else-inverse.js",
    "content": "const rule = require('../../../lib/rules/template-no-yield-block-params-to-else-inverse');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{{yield}}',\n  '{{yield \"some\" \"param\"}}',\n  '{{yield to=\"whatever\"}}',\n  '{{yield to=this.someValue}}',\n  '{{yield to=(get some this.map)}}',\n  '{{yield to=\"else\"}}',\n  '{{yield to=\"inverse\"}}',\n  '{{not-yield \"some\" \"param\" to=\"else\"}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{yield \"some\" \"param\" to=\"else\"}}',\n    output: null,\n    errors: [{ message: 'Yielding block params to else/inverse block is not allowed' }],\n  },\n  {\n    code: '{{yield \"some\" \"param\" to=\"inverse\"}}',\n    output: null,\n    errors: [{ message: 'Yielding block params to else/inverse block is not allowed' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-yield-block-params-to-else-inverse', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-yield-block-params-to-else-inverse', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-yield-only.js",
    "content": "const rule = require('../../../lib/rules/template-no-yield-only');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{{yield (hash someProp=someValue)}}',\n  '{{field}}',\n  '{{#yield}}{{/yield}}',\n  '<Yield/>',\n  '<yield/>',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{yield}}',\n    output: null,\n    errors: [{ messageId: 'noYieldOnly' }],\n  },\n  {\n    code: '     {{yield}}',\n    output: null,\n    errors: [{ messageId: 'noYieldOnly' }],\n  },\n  {\n    code: '\\n  {{yield}}\\n     ',\n    output: null,\n    errors: [{ messageId: 'noYieldOnly' }],\n  },\n  {\n    code: '\\n{{! some comment }}  {{yield}}\\n     ',\n    output: null,\n    errors: [{ messageId: 'noYieldOnly' }],\n  },\n  {\n    code: '{{!-- long-form comment --}}{{yield}}',\n    output: null,\n    errors: [{ messageId: 'noYieldOnly' }],\n  },\n  {\n    code: '<!-- html comment -->{{yield}}',\n    output: null,\n    errors: [{ messageId: 'noYieldOnly' }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-yield-only', rule, {\n  valid: validHbs.filter((entry) => !entry.includes('template-lint-disable')).map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-yield-only', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-no-yield-to-default.js",
    "content": "const rule = require('../../../lib/rules/template-no-yield-to-default');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE = 'A block named \"default\" is not valid';\n\nconst validHbs = [\n  '{{yield}}',\n  '{{yield to=\"title\"}}',\n  '{{has-block}}',\n  '{{has-block \"title\"}}',\n  '{{has-block-params}}',\n  '{{has-block-params \"title\"}}',\n  '{{hasBlock}}',\n  '{{hasBlock \"title\"}}',\n  '{{hasBlockParams}}',\n  '{{hasBlockParams \"title\"}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{yield to=\"default\"}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{has-block \"default\"}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{has-block-params \"default\"}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{hasBlock \"default\"}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{hasBlockParams \"default\"}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{if (has-block \"default\")}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{#if (has-block \"default\")}}{{/if}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{if (has-block-params \"default\")}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{#if (has-block-params \"default\")}}{{/if}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{if (hasBlock \"default\")}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{#if (hasBlock \"default\")}}{{/if}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{if (hasBlockParams \"default\")}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{#if (hasBlockParams \"default\")}}{{/if}}',\n    output: null,\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-no-yield-to-default', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-no-yield-to-default', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-quotes.js",
    "content": "const rule = require('../../../lib/rules/template-quotes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  { code: '{{component \"test\"}}', options: ['double'] },\n  { code: '{{hello x=\"test\"}}', options: ['double'] },\n  { code: '<input type=\"checkbox\">', options: ['double'] },\n  { code: \"{{component 'test'}}\", options: ['single'] },\n  { code: \"{{hello x='test'}}\", options: ['single'] },\n  { code: \"<input type='checkbox'>\", options: ['single'] },\n  {\n    code: '{{component \"test\"}} {{hello x=\\'test\\'}} <input type=\\'checkbox\\'> <input type=\"checkbox\">',\n    options: [{ curlies: false, html: false }],\n  },\n  {\n    code: \"{{component \\\"test\\\"}} {{hello x='test'}} <input type='checkbox'>\",\n    options: [{ curlies: false, html: 'single' }],\n  },\n  {\n    code: '{{component \"test\"}} <input type=\\'checkbox\\'> <input type=\"checkbox\">',\n    options: [{ curlies: 'double', html: false }],\n  },\n  {\n    code: \"<input type=\\\"checkbox\\\"> {{hello 'test' x='test'}}\",\n    options: [{ curlies: 'single', html: 'double' }],\n  },\n  {\n    code: '<input type=\\'checkbox\\'> {{hello \"test\" x=\"test\"}}',\n    options: [{ curlies: 'double', html: 'single' }],\n  },\n  {\n    code: \"{{component \\\"test\\\"}} {{hello x='test'}} <input type='checkbox'>\",\n    options: [false],\n  },\n  {\n    code: '{{component \\'test\\'}} <input type=\"checkbox\">',\n    options: [false],\n  },\n];\n\nconst invalidHbs = [\n  {\n    code: \"{{component 'one {{thing}} two'}}\",\n    output: '{{component \"one {{thing}} two\"}}',\n    options: ['double'],\n    errors: [{ message: 'you must use double quotes in templates' }],\n  },\n  {\n    code: \"{{component 'test'}}\",\n    output: '{{component \"test\"}}',\n    options: ['double'],\n    errors: [{ message: 'you must use double quotes in templates' }],\n  },\n  {\n    code: \"{{hello x='test'}}\",\n    output: '{{hello x=\"test\"}}',\n    options: ['double'],\n    errors: [{ message: 'you must use double quotes in templates' }],\n  },\n  {\n    code: \"<input type='checkbox'>\",\n    output: '<input type=\"checkbox\">',\n    options: ['double'],\n    errors: [{ message: 'you must use double quotes in templates' }],\n  },\n  {\n    code: '<img class=\\'a \"so-called\" btn {{this.otherClass}}\\'>',\n    output: null,\n    options: ['double'],\n    errors: [{ message: 'you must use double quotes in templates' }],\n  },\n  {\n    code: '{{component \"test\"}}',\n    output: \"{{component 'test'}}\",\n    options: ['single'],\n    errors: [{ message: 'you must use single quotes in templates' }],\n  },\n  {\n    code: '{{hello x=\"test\"}}',\n    output: \"{{hello x='test'}}\",\n    options: ['single'],\n    errors: [{ message: 'you must use single quotes in templates' }],\n  },\n  {\n    code: '<input type=\"checkbox\">',\n    output: \"<input type='checkbox'>\",\n    options: ['single'],\n    errors: [{ message: 'you must use single quotes in templates' }],\n  },\n  {\n    code: '<img alt=\"Abdul\\'s house\">',\n    output: null,\n    options: ['single'],\n    errors: [{ message: 'you must use single quotes in templates' }],\n  },\n  {\n    code: '{{helper \"Priya\\'s house\"}}',\n    output: null,\n    options: ['single'],\n    errors: [{ message: 'you must use single quotes in templates' }],\n  },\n  {\n    code: \"<input type=\\\"checkbox\\\"> {{hello 'test' x='test'}}\",\n    output: '<input type=\\'checkbox\\'> {{hello \"test\" x=\"test\"}}',\n    options: [{ curlies: 'double', html: 'single' }],\n    errors: [\n      {\n        message:\n          'you must use double quotes for Handlebars syntax and single quotes for HTML attributes in templates',\n      },\n      {\n        message:\n          'you must use double quotes for Handlebars syntax and single quotes for HTML attributes in templates',\n      },\n      {\n        message:\n          'you must use double quotes for Handlebars syntax and single quotes for HTML attributes in templates',\n      },\n    ],\n  },\n  {\n    code: '<input type=\\'checkbox\\'> {{hello \"test\" x=\"test\"}}',\n    output: \"<input type=\\\"checkbox\\\"> {{hello 'test' x='test'}}\",\n    options: [{ curlies: 'single', html: 'double' }],\n    errors: [\n      {\n        message:\n          'you must use double quotes for HTML attributes and single quotes for Handlebars syntax in templates',\n      },\n      {\n        message:\n          'you must use double quotes for HTML attributes and single quotes for Handlebars syntax in templates',\n      },\n      {\n        message:\n          'you must use double quotes for HTML attributes and single quotes for Handlebars syntax in templates',\n      },\n    ],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-quotes', rule, {\n  valid: validHbs\n    .filter(\n      (entry) =>\n        !entry.code.includes('<input type=\"checkbox\"> {{') &&\n        !entry.code.includes(\"<input type='checkbox'> {{\")\n    )\n    .map(wrapTemplate),\n  invalid: invalidHbs\n    .filter(\n      (entry) =>\n        !entry.code.includes('<input type=\"checkbox\"> {{') &&\n        !entry.code.includes(\"<input type='checkbox'> {{\")\n    )\n    .map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-quotes', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-aria-activedescendant-tabindex.js",
    "content": "const rule = require('../../../lib/rules/template-require-aria-activedescendant-tabindex');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE =\n  'A generic element using the aria-activedescendant attribute must have a tabindex';\n\nconst validHbs = [\n  '<div tabindex=\"-1\"></div>',\n  '<div aria-activedescendant=\"some-id\" tabindex=0></div>',\n  '<input aria-activedescendant=\"some-id\" />',\n  '<input aria-activedescendant={{foo}} tabindex={{0}} />',\n  '<div aria-activedescendant=\"option0\" tabindex=\"0\"></div>',\n  '<CustomComponent aria-activedescendant=\"choice1\" />',\n  '<CustomComponent aria-activedescendant=\"option1\" tabIndex=\"-1\" />',\n  '<CustomComponent aria-activedescendant={{foo}} tabindex={{bar}} />',\n  // tabindex=\"-1\" is focusable-but-not-tabbable — the canonical pattern for\n  // composite widgets that manage focus via roving focus / aria-activedescendant.\n  // See W3C APG — https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant\n  '<div aria-activedescendant=\"option0\" tabindex=\"-1\"></div>',\n  '<div aria-activedescendant={{foo}} tabindex={{-1}}></div>',\n  '<button aria-activedescendant=\"x\" tabindex=\"-1\"></button>',\n];\n\nconst invalidHbs = [\n  {\n    code: '<input aria-activedescendant=\"option0\" tabindex=\"-2\" />',\n    output: '<input aria-activedescendant=\"option0\" tabindex=\"0\" />',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<div aria-activedescendant={{bar}} />',\n    output: '<div aria-activedescendant={{bar}} tabindex=\"0\" />',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<div aria-activedescendant=\"fixme\" tabindex=-100></div>',\n    output: '<div aria-activedescendant=\"fixme\" tabindex=\"0\"></div>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<a aria-activedescendant=\"x\"></a>',\n    output: '<a aria-activedescendant=\"x\" tabindex=\"0\"></a>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-require-aria-activedescendant-tabindex', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-aria-activedescendant-tabindex', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-button-type.js",
    "content": "const rule = require('../../../lib/rules/template-require-button-type');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE = 'All `<button>` elements should have a valid `type` attribute';\n\nconst validHbs = [\n  '<button type=\"button\" />',\n  '<button type=\"button\">label</button>',\n  '<button type=\"submit\" />',\n  '<button type=\"reset\" />',\n  '<button type=\"{{buttonType}}\" />',\n  '<button type={{buttonType}} />',\n  '<div/>',\n  '<div></div>',\n  '<div type=\"randomType\"></div>',\n];\n\nconst invalidHbs = [\n  {\n    code: '<button/>',\n    output: '<button type=\"button\" />',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<button>label</button>',\n    output: '<button type=\"button\">label</button>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<button type=\"\" />',\n    output: '<button type=\"button\" />',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<button type=\"foo\" />',\n    output: '<button type=\"button\" />',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<button type=42 />',\n    output: '<button type=\"button\" />',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<form><button></button></form>',\n    output: '<form><button type=\"submit\"></button></form>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n];\n\nconst gjsValid = validHbs.map((code) => `<template>${code}</template>`);\n\nconst gjsInvalid = [\n  {\n    code: '<template><button/></template>',\n    output: '<template><button type=\"button\" /></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<template><button></button></template>',\n    output: '<template><button type=\"button\"></button></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<template><button>label</button></template>',\n    output: '<template><button type=\"button\">label</button></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<template><button type=\"\" /></template>',\n    output: '<template><button type=\"button\" /></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<template><button type=\"foo\" /></template>',\n    output: '<template><button type=\"button\" /></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<template><button type=42 /></template>',\n    output: '<template><button type=\"button\" /></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '<template><form><button></button></form></template>',\n    output: '<template><form><button type=\"submit\"></button></form></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '/** silly example <button> usage */ <template><button></button></template>',\n    output:\n      '/** silly example <button> usage */ <template><button type=\"button\"></button></template>',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n];\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-require-button-type', rule, {\n  valid: gjsValid,\n  invalid: gjsInvalid,\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-require-button-type', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-context-role.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-require-context-role');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-context-role', rule, {\n  valid: [\n    `<template>\n      <ul role=\"list\">\n        <li role=\"listitem\">Item</li>\n      </ul>\n    </template>`,\n    `<template>\n      <div role=\"tablist\">\n        <div role=\"tab\">Tab 1</div>\n      </div>\n    </template>`,\n    `<template>\n      <div role=\"menu\">\n        <div role=\"menuitem\">Item</div>\n      </div>\n    </template>`,\n    `<template>\n      <div role=\"button\">No context needed</div>\n    </template>`,\n\n    '<template><div role=\"list\"><div role=\"listitem\">Item One</div><div role=\"listitem\">Item Two</div></div></template>',\n    '<template><div role=\"group\"><div role=\"listitem\">Item One</div><div role=\"listitem\">Item Two</div></div></template>',\n    '<template><div role=\"row\"><div role=\"columnheader\">Item One</div></div></template>',\n    '<template><div role=\"gridcell\">Item One</div></template>',\n    '<template><div role=\"row\">{{yield}}</div></template>',\n    '<template><div role=\"row\"><div role=\"gridcell\">Item One</div></div></template>',\n    '<template><div role=\"row\"><br>{{#if a}}<div role=\"gridcell\">Item One</div>{{/if}}</div></template>',\n    '<template><div role=\"group\"><div role=\"menuitem\">Item One</div></div></template>',\n    '<template><div role=\"menu\"><div role=\"menuitem\">Item One</div></div></template>',\n    '<template><div role=\"menubar\"><div role=\"menuitem\">Item One</div></div></template>',\n    '<template><div role=\"menu\"><div role=\"menuitemcheckbox\">Item One</div></div></template>',\n    '<template><div role=\"menubar\"><div role=\"menuitemcheckbox\">Item One</div></div></template>',\n    '<template><div role=\"group\"><div role=\"menuitemradio\">Item One</div></div></template>',\n    '<template><div role=\"menu\"><div role=\"menuitemradio\">Item One</div></div></template>',\n    '<template><div role=\"menubar\"><div role=\"menuitemradio\">Item One</div></div></template>',\n    '<template><div role=\"menubar\"><div role=\"presentation\"><a role=\"menuitem\">Item One</a></div></div></template>',\n    '<template><div role=\"listbox\"><div role=\"option\">Item One</div></div></template>',\n    '<template><div role=\"grid\"><div role=\"row\">Item One</div></div></template>',\n    '<template><div role=\"rowgroup\"><div role=\"row\">Item One</div></div></template>',\n    '<template><div role=\"treegrid\"><div role=\"row\">Item One</div></div></template>',\n    '<template><div aria-hidden=\"true\" role=\"tablist\"><div role=\"treeitem\">Item One</div></div></template>',\n    '<template><div role=\"grid\"><div role=\"rowgroup\">Item One</div></div></template>',\n    '<template><div role=\"row\"><div role=\"rowheader\">Item One</div></div></template>',\n    '<template><div role=\"tablist\"><div role=\"tab\">Item One</div></div></template>',\n    '<template><div role=\"group\"><div role=\"treeitem\">Item One</div></div></template>',\n    '<template><div role=\"tree\"><div role=\"treeitem\">Item One</div></div></template>',\n    '<template><div role=\"list\">{{#each someList as |item|}}{{list-item item=item}}{{/each}}</div></template>',\n    '<template><div role=\"list\">{{#each someList as |item|}}<ListItem @item={{item}} />{{/each}}</div></template>',\n    '<template><div role=\"list\">{{#if this.show}}{{#each someList as |item|}}<ListItem @item={{item}} />{{/each}}{{/if}}</div></template>',\n    '<template><div role=\"row\"><div role=\"cell\">One</div></div></template>',\n    '<template><div role=\"table\"><div role=\"row\"><div role=\"cell\">One</div></div></div></template>',\n    `<template><typeahead.list role=\"list\">\n      <:content as |items|>\n        {{#each items as |job idx|}}\n          <result role=\"listitem\">\n            ...\n          </result>\n        {{/each}}\n        </:content>\n    </typeahead.list></template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <div>\n          <div role=\"listitem\">Item</div>\n        </div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"listitem\" must be contained in an element with one of these roles: group, list',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div>\n          <div role=\"tab\">Tab 1</div>\n        </div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Role \"tab\" must be contained in an element with one of these roles: tablist',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div>\n          <div role=\"menuitem\">Item</div>\n        </div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><div role=\"tablist\"><div role=\"treeitem\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"treeitem\" must be contained in an element with one of these roles: group, tree',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"columnheader\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"columnheader\" must be contained in an element with one of these roles: row',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"gridcell\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        { message: 'Role \"gridcell\" must be contained in an element with one of these roles: row' },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"listitem\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"listitem\" must be contained in an element with one of these roles: group, list',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"menuitem\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"menuitemcheckbox\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitemcheckbox\" must be contained in an element with one of these roles: menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"menuitemradio\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitemradio\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"option\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Role \"option\" must be contained in an element with one of these roles: listbox',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"row\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"row\" must be contained in an element with one of these roles: grid, rowgroup, table, treegrid',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"rowgroup\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"rowgroup\" must be contained in an element with one of these roles: grid, table, treegrid',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"rowheader\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"rowheader\" must be contained in an element with one of these roles: grid, row',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"tab\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        { message: 'Role \"tab\" must be contained in an element with one of these roles: tablist' },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"treeitem\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"treeitem\" must be contained in an element with one of these roles: group, tree',\n        },\n      ],\n    },\n    {\n      code: '<template><div><div role=\"cell\">Item One</div></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Role \"cell\" must be contained in an element with one of these roles: row',\n        },\n      ],\n    },\n    {\n      code: '<template><div role=\"menu\"><div><a role=\"menuitem\">Item One</a></div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<template><div role=\"menu\"><div role=\"button\"><a role=\"menuitem\">Item One</a></div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      // aria-hidden on a non-immediate ancestor must NOT suppress the rule\n      // (upstream only honors aria-hidden on the immediate parent)\n      code: '<template><div aria-hidden=\"true\"><div><div role=\"listitem\">Item</div></div></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"listitem\" must be contained in an element with one of these roles: group, list',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-context-role', rule, {\n  valid: [\n    '<div role=\"list\"><div role=\"listitem\">Item One</div><div role=\"listitem\">Item Two</div></div>',\n    '<div role=\"group\"><div role=\"listitem\">Item One</div><div role=\"listitem\">Item Two</div></div>',\n    '<div role=\"row\"><div role=\"columnheader\">Item One</div></div>',\n    '<div role=\"gridcell\">Item One</div>',\n    '<div role=\"row\">{{yield}}</div>',\n    '<div role=\"row\"><div role=\"gridcell\">Item One</div></div>',\n    '<div role=\"row\"><br>{{#if a}}<div role=\"gridcell\">Item One</div>{{/if}}</div>',\n    '<div role=\"group\"><div role=\"menuitem\">Item One</div></div>',\n    '<div role=\"menu\"><div role=\"menuitem\">Item One</div></div>',\n    '<div role=\"menubar\"><div role=\"menuitem\">Item One</div></div>',\n    '<div role=\"menu\"><div role=\"menuitemcheckbox\">Item One</div></div>',\n    '<div role=\"menubar\"><div role=\"menuitemcheckbox\">Item One</div></div>',\n    '<div role=\"group\"><div role=\"menuitemradio\">Item One</div></div>',\n    '<div role=\"menu\"><div role=\"menuitemradio\">Item One</div></div>',\n    '<div role=\"menubar\"><div role=\"menuitemradio\">Item One</div></div>',\n    '<div role=\"menubar\"><div role=\"presentation\"><a role=\"menuitem\">Item One</a></div></div>',\n    '<div role=\"listbox\"><div role=\"option\">Item One</div></div>',\n    '<div role=\"grid\"><div role=\"row\">Item One</div></div>',\n    '<div role=\"rowgroup\"><div role=\"row\">Item One</div></div>',\n    '<div role=\"treegrid\"><div role=\"row\">Item One</div></div>',\n    '<div aria-hidden=\"true\" role=\"tablist\"><div role=\"treeitem\">Item One</div></div>',\n    '<div role=\"grid\"><div role=\"rowgroup\">Item One</div></div>',\n    '<div role=\"row\"><div role=\"rowheader\">Item One</div></div>',\n    '<div role=\"tablist\"><div role=\"tab\">Item One</div></div>',\n    '<div role=\"group\"><div role=\"treeitem\">Item One</div></div>',\n    '<div role=\"tree\"><div role=\"treeitem\">Item One</div></div>',\n    '<div role=\"list\">{{#each someList as |item|}}{{list-item item=item}}{{/each}}</div>',\n    '<div role=\"list\">{{#each someList as |item|}}<ListItem @item={{item}} />{{/each}}</div>',\n    '<div role=\"list\">{{#if this.show}}{{#each someList as |item|}}<ListItem @item={{item}} />{{/each}}{{/if}}</div>',\n    '<div role=\"row\"><div role=\"cell\">One</div></div>',\n    '<div role=\"table\"><div role=\"row\"><div role=\"cell\">One</div></div></div>',\n    `<typeahead.list role=\"list\">\n      <:content as |items|>\n        {{#each items as |job idx|}}\n          <result role=\"listitem\">\n            ...\n          </result>\n        {{/each}}\n        </:content>\n    </typeahead.list>`,\n  ],\n  invalid: [\n    {\n      code: '<div role=\"tablist\"><div role=\"treeitem\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"treeitem\" must be contained in an element with one of these roles: group, tree',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"columnheader\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"columnheader\" must be contained in an element with one of these roles: row',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"gridcell\">Item One</div></div>',\n      output: null,\n      errors: [\n        { message: 'Role \"gridcell\" must be contained in an element with one of these roles: row' },\n      ],\n    },\n    {\n      code: '<div><div role=\"listitem\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"listitem\" must be contained in an element with one of these roles: group, list',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"menuitem\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"menuitemcheckbox\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitemcheckbox\" must be contained in an element with one of these roles: menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"menuitemradio\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitemradio\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"option\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message: 'Role \"option\" must be contained in an element with one of these roles: listbox',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"row\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"row\" must be contained in an element with one of these roles: grid, rowgroup, table, treegrid',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"rowgroup\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"rowgroup\" must be contained in an element with one of these roles: grid, table, treegrid',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"rowheader\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"rowheader\" must be contained in an element with one of these roles: grid, row',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"tab\">Item One</div></div>',\n      output: null,\n      errors: [\n        { message: 'Role \"tab\" must be contained in an element with one of these roles: tablist' },\n      ],\n    },\n    {\n      code: '<div><div role=\"treeitem\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"treeitem\" must be contained in an element with one of these roles: group, tree',\n        },\n      ],\n    },\n    {\n      code: '<div><div role=\"cell\">Item One</div></div>',\n      output: null,\n      errors: [\n        {\n          message: 'Role \"cell\" must be contained in an element with one of these roles: row',\n        },\n      ],\n    },\n    {\n      code: '<div role=\"menu\"><div><a role=\"menuitem\">Item One</a></div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      code: '<div role=\"menu\"><div role=\"button\"><a role=\"menuitem\">Item One</a></div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"menuitem\" must be contained in an element with one of these roles: group, menu, menubar',\n        },\n      ],\n    },\n    {\n      // aria-hidden on a non-immediate ancestor must NOT suppress the rule\n      code: '<div aria-hidden=\"true\"><div><div role=\"listitem\">Item</div></div></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Role \"listitem\" must be contained in an element with one of these roles: group, list',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-each-key.js",
    "content": "const rule = require('../../../lib/rules/template-require-each-key');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE = '{{#each}} helper requires a valid key value to avoid performance issues';\n\nconst validHbs = [\n  '{{#each this.items key=\"id\" as |item|}} {{item.name}} {{/each}}',\n  '{{#each this.items key=\"deeply.nested.id\" as |item|}} {{item.name}} {{/each}}',\n  '{{#each this.items key=\"@index\" as |item|}} {{item.name}} {{/each}}',\n  '{{#each this.items key=\"@identity\" as |item|}} {{item.name}} {{/each}}',\n  '{{#if foo}}{{/if}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{#each this.items as |item|}} {{item.name}} {{/each}}',\n    output: '{{#each this.items key=\"@identity\" as |item|}} {{item.name}} {{/each}}',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{#each this.items key=\"@invalid\" as |item|}} {{item.name}} {{/each}}',\n    output: '{{#each this.items key=\"@identity\" as |item|}} {{item.name}} {{/each}}',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n  {\n    code: '{{#each this.items key=\"\" as |item|}} {{item.name}} {{/each}}',\n    output: '{{#each this.items key=\"@identity\" as |item|}} {{item.name}} {{/each}}',\n    errors: [{ message: ERROR_MESSAGE }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-require-each-key', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-each-key', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-form-method.js",
    "content": "const rule = require('../../../lib/rules/template-require-form-method');\nconst RuleTester = require('eslint').RuleTester;\n\nconst DEFAULT_ERROR =\n  'All `<form>` elements should have `method` attribute with value of `POST,GET,DIALOG`';\n\nconst validHbs = [\n  {\n    options: [{ allowedMethods: ['get'] }],\n    code: '<form method=\"GET\"></form>',\n  },\n  // No options → default-enabled with POST,GET,DIALOG.\n  '<form method=\"POST\"></form>',\n  '<form method=\"post\"></form>',\n  '<form method=\"GET\"></form>',\n  '<form method=\"get\"></form>',\n  '<form method=\"DIALOG\"></form>',\n  '<form method=\"dialog\"></form>',\n  '<form method=\"{{formMethod}}\"></form>',\n  '<form method={{formMethod}}></form>',\n  '<div/>',\n  '<div></div>',\n  '<div method=\"randomType\"></div>',\n  // Explicit `true` behaves identically to no options.\n  { options: [true], code: '<form method=\"POST\"></form>' },\n  // Explicit `false` disables the rule.\n  { options: [false], code: '<form></form>' },\n];\n\nconst invalidHbs = [\n  // Default-enabled: no options → rule active with POST,GET,DIALOG.\n  {\n    code: '<form></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n  {\n    code: '<form method=\"NOT_A_VALID_METHOD\"></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n  {\n    options: [{ allowedMethods: ['get'] }],\n    code: '<form method=\"POST\"></form>',\n    output: '<form method=\"GET\"></form>',\n    errors: [\n      { message: 'All `<form>` elements should have `method` attribute with value of `GET`' },\n    ],\n  },\n  {\n    options: [{ allowedMethods: ['POST'] }],\n    code: '<form method=\"GET\"></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [\n      { message: 'All `<form>` elements should have `method` attribute with value of `POST`' },\n    ],\n  },\n  {\n    options: [true],\n    code: '<form></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n  {\n    options: [true],\n    code: '<form method=\"\"></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n  {\n    options: [true],\n    code: '<form method=42></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n  {\n    options: [true],\n    code: '<form method=\" ge t \"></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n  {\n    options: [true],\n    code: '<form method=\" pos t \"></form>',\n    output: '<form method=\"POST\"></form>',\n    errors: [{ message: DEFAULT_ERROR }],\n  },\n];\n\nfunction wrapTemplate(entry) {\n  if (typeof entry === 'string') {\n    return `<template>${entry}</template>`;\n  }\n\n  return {\n    ...entry,\n    code: `<template>${entry.code}</template>`,\n    output: entry.output ? `<template>${entry.output}</template>` : entry.output,\n  };\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-require-form-method', rule, {\n  valid: validHbs.map(wrapTemplate),\n  invalid: invalidHbs.map(wrapTemplate),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-form-method', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n\n// parseConfig should throw on an invalid `allowedMethods` entry so that\n// misconfiguration is surfaced immediately rather than silently ignored.\ndescribe('template-require-form-method invalid configuration', () => {\n  const { Linter } = require('eslint');\n\n  function lintWith(options) {\n    const linter = new Linter();\n    linter.defineParser('ember-eslint-parser/hbs', require('ember-eslint-parser/hbs'));\n    linter.defineRule('template-require-form-method', rule);\n    return linter.verify('<form method=\"POST\"></form>', {\n      parser: 'ember-eslint-parser/hbs',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      rules: { 'template-require-form-method': ['error', options] },\n    });\n  }\n\n  test('throws on unknown method in allowedMethods', () => {\n    expect(() => lintWith({ allowedMethods: ['PATCH'] })).toThrow(/invalid configuration/);\n  });\n\n  test('throws on mixed valid/invalid method list', () => {\n    expect(() => lintWith({ allowedMethods: ['GET', 'BOGUS'] })).toThrow(/invalid configuration/);\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-has-block-helper.js",
    "content": "const rule = require('../../../lib/rules/template-require-has-block-helper');\nconst RuleTester = require('eslint').RuleTester;\n\nconst validHbs = [\n  '{{has-block}}',\n  '{{has-block-params}}',\n  '{{something-else}}',\n  '{{component test=(if (has-block) \"true\")}}',\n  '{{component test=(if (has-block-params) \"true\")}}',\n  '<SomeComponent someProp={{has-block}}',\n  '<SomeComponent someProp={{has-block-params}}',\n  '{{#if (has-block)}}{{/if}}',\n  '{{#if (has-block-params)}}{{/if}}',\n];\n\nconst invalidHbs = [\n  {\n    code: '{{hasBlock}}',\n    output: '{{has-block}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{hasBlockParams}}',\n    output: '{{has-block-params}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{if hasBlock \"true\" \"false\"}}',\n    output: '{{if (has-block) \"true\" \"false\"}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{if hasBlockParams \"true\" \"false\"}}',\n    output: '{{if (has-block-params) \"true\" \"false\"}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{if (hasBlock) \"true\" \"false\"}}',\n    output: '{{if (has-block) \"true\" \"false\"}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{if (hasBlockParams) \"true\" \"false\"}}',\n    output: '{{if (has-block-params) \"true\" \"false\"}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{if (hasBlock \"inverse\") \"true\" \"false\"}}',\n    output: '{{if (has-block \"inverse\") \"true\" \"false\"}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{if (hasBlockParams \"inverse\") \"true\" \"false\"}}',\n    output: '{{if (has-block-params \"inverse\") \"true\" \"false\"}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{component test=(if hasBlock \"true\")}}',\n    output: '{{component test=(if (has-block) \"true\")}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{component test=(if hasBlockParams \"true\")}}',\n    output: '{{component test=(if (has-block-params) \"true\")}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{#if hasBlock}}{{/if}}',\n    output: '{{#if (has-block)}}{{/if}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{#if hasBlockParams}}{{/if}}',\n    output: '{{#if (has-block-params)}}{{/if}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{#if (hasBlock)}}{{/if}}',\n    output: '{{#if (has-block)}}{{/if}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{#if (hasBlockParams)}}{{/if}}',\n    output: '{{#if (has-block-params)}}{{/if}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{#if (hasBlock \"inverse\")}}{{/if}}',\n    output: '{{#if (has-block \"inverse\")}}{{/if}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{#if (hasBlockParams \"inverse\")}}{{/if}}',\n    output: '{{#if (has-block-params \"inverse\")}}{{/if}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '<button name={{hasBlock}}></button>',\n    output: '<button name={{has-block}}></button>',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '<button name={{hasBlockParams}}></button>',\n    output: '<button name={{has-block-params}}></button>',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '<button name={{hasBlock \"inverse\"}}></button>',\n    output: '<button name={{has-block \"inverse\"}}></button>',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '<button name={{hasBlockParams \"inverse\"}}></button>',\n    output: '<button name={{has-block-params \"inverse\"}}></button>',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n  {\n    code: '{{#if (or isLoading hasLoadFailed hasBlock)}}...{{/if}}',\n    output: '{{#if (or isLoading hasLoadFailed (has-block))}}...{{/if}}',\n    errors: [{ message: '`hasBlock` is deprecated. Use the `has-block` helper instead.' }],\n  },\n  {\n    code: '{{#if (or isLoading hasLoadFailed hasBlockParams)}}...{{/if}}',\n    output: '{{#if (or isLoading hasLoadFailed (has-block-params))}}...{{/if}}',\n    errors: [\n      { message: '`hasBlockParams` is deprecated. Use the `has-block-params` helper instead.' },\n    ],\n  },\n];\n\nfunction wrapTemplate(template) {\n  if (template === '<SomeComponent someProp={{has-block}}') {\n    return '<template><SomeComponent someProp={{has-block}} /></template>';\n  }\n\n  if (template === '<SomeComponent someProp={{has-block-params}}') {\n    return '<template><SomeComponent someProp={{has-block-params}} /></template>';\n  }\n\n  return `<template>${template}</template>`;\n}\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-require-has-block-helper', rule, {\n  valid: [\n    ...validHbs.map(wrapTemplate),\n    // GJS/GTS: an imported/local hasBlock binding is a user's own reference,\n    // not the Glimmer built-in — rewriting to `has-block` would change semantics.\n    `import hasBlock from './my-has-block';\n     export default <template>{{hasBlock}}</template>;`,\n    `const hasBlockParams = () => true;\n     export default <template>{{hasBlockParams}}</template>;`,\n  ],\n  invalid: invalidHbs.map((test) => ({\n    code: wrapTemplate(test.code),\n    output: wrapTemplate(test.output),\n    errors: test.errors,\n  })),\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-has-block-helper', rule, {\n  valid: validHbs,\n  invalid: invalidHbs,\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-iframe-src-attribute.js",
    "content": "const rule = require('../../../lib/rules/template-require-iframe-src-attribute');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE =\n  'Security Risk: `<iframe>` must include a static `src` attribute. Otherwise, CSP `frame-src` is bypassed and `about:blank` inherits parent origin, creating an elevated-privilege frame.';\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-iframe-src-attribute', rule, {\n  valid: [\n    '<template><iframe src=\"about:blank\"></iframe></template>',\n    '<template><iframe src=\"/safe-path\" {{this.setFrameElement}}></iframe></template>',\n    '<template><iframe src=\"data:text/html,<h1>safe</h1>\"></iframe></template>',\n    '<template><iframe src=\"\"></iframe></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><iframe {{this.setFrameElement}}></iframe></template>',\n      output: '<template><iframe src=\"about:blank\" {{this.setFrameElement}}></iframe></template>',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: '<template><iframe></iframe></template>',\n      output: '<template><iframe src=\"about:blank\"></iframe></template>',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: '<template><iframe ...attributes id=\"foo\"></iframe></template>',\n      output: '<template><iframe ...attributes id=\"foo\" src=\"about:blank\"></iframe></template>',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-iframe-src-attribute', rule, {\n  valid: [\n    '<iframe src=\"about:blank\"></iframe>',\n    '<iframe src=\"/safe-path\" {{this.setFrameElement}}></iframe>',\n    '<iframe src=\"data:text/html,<h1>safe</h1>\"></iframe>',\n    '<iframe src=\"\"></iframe>',\n  ],\n  invalid: [\n    {\n      code: '<iframe {{this.setFrameElement}}></iframe>',\n      output: '<iframe src=\"about:blank\" {{this.setFrameElement}}></iframe>',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: '<iframe></iframe>',\n      output: '<iframe src=\"about:blank\"></iframe>',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n    {\n      code: '<iframe ...attributes id=\"foo\"></iframe>',\n      output: '<iframe ...attributes id=\"foo\" src=\"about:blank\"></iframe>',\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-iframe-title.js",
    "content": "const rule = require('../../../lib/rules/template-require-iframe-title');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-iframe-title', rule, {\n  valid: [\n    '<template><iframe title=\"Video\"></iframe></template>',\n    '<template><iframe title=\"Map\" src=\"/map\"></iframe></template>',\n    '<template><iframe aria-hidden=\"true\"></iframe></template>',\n    '<template><iframe hidden></iframe></template>',\n\n    '<template><iframe title=\"Welcome to the Matrix!\" /></template>',\n    '<template><iframe title={{someValue}} /></template>',\n    '<template><iframe title=\"\" aria-hidden /></template>',\n    '<template><iframe title=\"\" hidden /></template>',\n    // Mustache string literals resolve to their static value — non-empty\n    // literals supply an accessible name the same as a text node.\n    '<template><iframe title={{\"My frame\"}} /></template>',\n    '<template><iframe title=\"foo\" /><iframe title=\"bar\" /></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><iframe src=\"/content\"></iframe></template>',\n      output: null,\n      errors: [{ messageId: 'missingTitle' }],\n    },\n    {\n      code: '<template><iframe title=\"\"></iframe></template>',\n      output: null,\n      errors: [{ messageId: 'emptyTitle' }],\n    },\n    // Empty string-literal mustaches and concat-with-empty-string-literal\n    // resolve to \"\" via the static-attr-value handling and are flagged the\n    // same as the text-node empty case. Closes a bypass jsx-a11y already\n    // catches via getLiteralPropValue.\n    {\n      code: '<template><iframe title={{\"\"}} /></template>',\n      output: null,\n      errors: [{ messageId: 'emptyTitle' }],\n    },\n    {\n      code: '<template><iframe title=\"{{\"\"}}\" /></template>',\n      output: null,\n      errors: [{ messageId: 'emptyTitle' }],\n    },\n\n    {\n      // Both occurrences are reported with a shared `#N` index.\n      code: '<template><iframe title=\"foo\" /><iframe title=\"foo\" /></template>',\n      output: null,\n      errors: [\n        { message: 'This title is not unique. #1' },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n      ],\n    },\n    {\n      // Three duplicates → the first occurrence is re-reported on every\n      // collision, so iframe #1 is flagged twice (once per later collision)\n      // and iframes #2 and #3 are each flagged once. ESLint sorts by source\n      // location, so the two first-occurrence reports (same location) come\n      // before the two later occurrences.\n      code: '<template><iframe title=\"foo\" /><iframe title=\"foo\" /><iframe title=\"foo\" /></template>',\n      output: null,\n      errors: [\n        { message: 'This title is not unique. #1' },\n        { message: 'This title is not unique. #1' },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n      ],\n    },\n    {\n      // Two distinct duplicate groups → 4 reports, indices #1 and #2.\n      // ESLint sorts errors by source location; the two \"first-occurrence\"\n      // reports attach to the first two iframes and so precede the two\n      // \"other-occurrence\" reports attached to the later iframes.\n      code: '<template><iframe title=\"foo\" /><iframe title=\"boo\" /><iframe title=\"foo\" /><iframe title=\"boo\" /></template>',\n      output: null,\n      errors: [\n        { message: 'This title is not unique. #1' },\n        { message: 'This title is not unique. #2' },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"boo\" already used for different iframe. #2',\n        },\n      ],\n    },\n    {\n      code: '<template><iframe src=\"12\" /></template>',\n      output: null,\n      errors: [{ messageId: 'missingTitle' }],\n    },\n    {\n      code: '<template><iframe src=\"12\" title={{false}} /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'boolean' } }],\n    },\n    {\n      code: '<template><iframe src=\"12\" title=\"{{false}}\" /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'boolean' } }],\n    },\n    {\n      code: '<template><iframe src=\"12\" title=\"\" /></template>',\n      output: null,\n      errors: [{ messageId: 'emptyTitle' }],\n    },\n\n    // Mustache literals that don't coerce to a useful accessible name.\n    {\n      code: '<template><iframe title={{null}} /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'null' } }],\n    },\n    {\n      code: '<template><iframe title={{undefined}} /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'undefined' } }],\n    },\n    {\n      code: '<template><iframe title={{42}} /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'number' } }],\n    },\n    {\n      code: '<template><iframe title=\"{{null}}\" /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'null' } }],\n    },\n    {\n      code: '<template><iframe title=\"{{undefined}}\" /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'undefined' } }],\n    },\n    {\n      code: '<template><iframe title=\"{{42}}\" /></template>',\n      output: null,\n      errors: [{ messageId: 'invalidTitleLiteral', data: { literalType: 'number' } }],\n    },\n\n    // Whitespace-only title is flagged by default (authoring hygiene).\n    {\n      code: '<template><iframe title=\"   \" /></template>',\n      output: null,\n      errors: [{ messageId: 'emptyTitle' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-iframe-title', rule, {\n  valid: [\n    '<iframe title=\"Welcome to the Matrix!\" />',\n    '<iframe title={{someValue}} />',\n    '<iframe title=\"\" aria-hidden />',\n    '<iframe title=\"\" hidden />',\n    '<iframe title=\"foo\" /><iframe title=\"bar\" />',\n  ],\n  invalid: [\n    {\n      code: '<iframe title=\"foo\" /><iframe title=\"foo\" />',\n      output: null,\n      errors: [\n        { message: 'This title is not unique. #1' },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n      ],\n    },\n    {\n      // Three duplicates: the first occurrence is re-reported on every\n      // collision, so iframe #1 is flagged twice and each later iframe once.\n      code: '<iframe title=\"foo\" /><iframe title=\"foo\" /><iframe title=\"foo\" />',\n      output: null,\n      errors: [\n        { message: 'This title is not unique. #1' },\n        { message: 'This title is not unique. #1' },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n      ],\n    },\n    {\n      code: '<iframe title=\"foo\" /><iframe title=\"boo\" /><iframe title=\"foo\" /><iframe title=\"boo\" />',\n      output: null,\n      errors: [\n        { message: 'This title is not unique. #1' },\n        { message: 'This title is not unique. #2' },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"foo\" already used for different iframe. #1',\n        },\n        {\n          message:\n            '<iframe> elements must have a unique title property. Value title=\"boo\" already used for different iframe. #2',\n        },\n      ],\n    },\n    {\n      code: '<iframe src=\"12\" />',\n      output: null,\n      errors: [{ message: '<iframe> elements must have a unique title property.' }],\n    },\n    {\n      code: '<iframe src=\"12\" title={{false}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '<iframe title> must be a non-empty string. Got boolean literal, which does not describe the frame contents.',\n        },\n      ],\n    },\n    {\n      code: '<iframe src=\"12\" title=\"{{false}}\" />',\n      output: null,\n      errors: [\n        {\n          message:\n            '<iframe title> must be a non-empty string. Got boolean literal, which does not describe the frame contents.',\n        },\n      ],\n    },\n    {\n      code: '<iframe src=\"12\" title=\"\" />',\n      output: null,\n      errors: [{ message: '<iframe> elements must have a unique title property.' }],\n    },\n\n    // hbs parity with gjs for the other non-string mustache literals\n    // (boolean true / null / undefined / number).\n    {\n      code: '<iframe title={{true}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '<iframe title> must be a non-empty string. Got boolean literal, which does not describe the frame contents.',\n        },\n      ],\n    },\n    {\n      code: '<iframe title={{null}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '<iframe title> must be a non-empty string. Got null literal, which does not describe the frame contents.',\n        },\n      ],\n    },\n    {\n      code: '<iframe title={{undefined}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '<iframe title> must be a non-empty string. Got undefined literal, which does not describe the frame contents.',\n        },\n      ],\n    },\n    {\n      code: '<iframe title={{42}} />',\n      output: null,\n      errors: [\n        {\n          message:\n            '<iframe title> must be a non-empty string. Got number literal, which does not describe the frame contents.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-input-label.js",
    "content": "const eslint = require('eslint');\nconst rule = require('../../../lib/rules/template-require-input-label');\n\nconst { RuleTester } = eslint;\n\nconst NO_LABEL = 'form elements require a valid associated label.';\nconst MULTIPLE_LABELS = 'form elements should not have multiple labels.';\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-input-label', rule, {\n  valid: [\n    '<template><label>LabelText<input /></label></template>',\n    '<template><label><input />LabelText</label></template>',\n    '<template><label>Label Text<div><input /></div></label></template>',\n    '<template><input id=\"probablyHasLabel\" /></template>',\n    '<template><input aria-label={{labelText}} /></template>',\n    '<template><input aria-labelledby=\"someIdValue\" /></template>',\n    // https://github.com/ember-template-lint/ember-template-lint/issues/3388\n    // id alone does not establish a labeling relationship — a <label for> is\n    // needed and cannot be verified statically. Only aria-label/labelledby\n    // should count.\n    '<template><input id=\"hello\" aria-label=\"hello\" /></template>',\n    '<template><input id=\"hello\" aria-labelledby=\"someIdValue\" /></template>',\n    '<template><div></div></template>',\n    '<template><Input id=\"foo\" /></template>',\n    '<template>{{input id=\"foo\"}}</template>',\n    '<template><input ...attributes /></template>',\n    '<template><label>LabelText<textarea /></label></template>',\n    '<template><textarea id=\"probablyHasLabel\" /></template>',\n    '<template><textarea aria-label={{labelText}} /></template>',\n    '<template><textarea aria-labelledby=\"someIdValue\" /></template>',\n    '<template><Textarea id=\"foo\" /></template>',\n    '<template>{{textarea id=\"foo\"}}</template>',\n    '<template><textarea ...attributes /></template>',\n    '<template><label>LabelText<select></select></label></template>',\n    '<template><select id=\"probablyHasLabel\"></select></template>',\n    '<template><select aria-label={{labelText}}></select></template>',\n    '<template><select aria-labelledby=\"someIdValue\"></select></template>',\n    '<template><select ...attributes></select></template>',\n    '<template><input type=\"hidden\" /></template>',\n    '<template><Input type=\"hidden\" /></template>',\n    '<template>{{input type=\"hidden\"}}</template>',\n    // In GJS/GTS with no @ember/component import, <Input>/<Textarea> are\n    // user-authored components — do not treat them as the built-in.\n    { filename: 'layout.gjs', code: '<template><Input /></template>' },\n    { filename: 'layout.gts', code: '<template><Textarea /></template>' },\n    // In GJS/GTS, {{input}} / {{textarea}} are user-imported bindings, not\n    // the classic Ember helpers — skip the mustache-form check.\n    { filename: 'layout.gjs', code: '<template>{{input}}</template>' },\n    { filename: 'layout.gts', code: '<template>{{textarea}}</template>' },\n    // Built-in <Input> imported from @ember/component, wrapped in a label.\n    {\n      filename: 'layout.gjs',\n      code: \"import { Input } from '@ember/component';\\n<template><label>Name <Input /></label></template>\",\n    },\n    {\n      code: '<template><CustomLabel><input /></CustomLabel></template>',\n      options: [{ labelTags: ['CustomLabel'] }],\n    },\n    {\n      code: '<template><web-label><input /></web-label></template>',\n      options: [{ labelTags: [/web-label/] }],\n    },\n    {\n      code: '<template><input /></template>',\n      options: [false],\n    },\n  ],\n  invalid: [\n    {\n      code: '<template><my-label><input /></my-label></template>',\n      output: null,\n      options: [{ labelTags: [/web-label/] }],\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><div><input /></div></template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><input title=\"some title value\" /></template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><label><input></label></template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><div>{{input}}</div></template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><input aria-label=\"first label\" aria-labelledby=\"second label\"></template>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '<template><label>Input label<input aria-label=\"Custom label\"></label></template>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      // id is irrelevant for labelling here — wrapping <label> + aria-label is\n      // still multiple labels.\n      code: '<template><label>Input label<input id=\"foo\" aria-label=\"Custom label\"></label></template>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '<template>{{input type=\"button\"}}</template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template>{{input type=myType}}</template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><textarea /></template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><textarea aria-label=\"first label\" aria-labelledby=\"second label\" /></template>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '<template><select></select></template>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<template><select aria-label=\"first label\" aria-labelledby=\"second label\" /></template>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    // Built-in <Input> imported from @ember/component in GJS → flagged.\n    {\n      filename: 'layout.gjs',\n      code: \"import { Input } from '@ember/component';\\n<template><Input /></template>\",\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    // Renamed import of <Textarea> from @ember/component in GTS → flagged.\n    {\n      filename: 'layout.gts',\n      code: \"import { Textarea as TA } from '@ember/component';\\n<template><TA /></template>\",\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-input-label', rule, {\n  valid: [\n    '<label>LabelText<input /></label>',\n    '<label><input />LabelText</label>',\n    '<label>LabelText<Input /></label>',\n    '<label><Input />LabelText</label>',\n    '<label>Label Text<div><input /></div></label>',\n    '<label>text<Input id=\"foo\" /></label>',\n    '<label>Text here<Input /></label>',\n    '<input id=\"probablyHasLabel\" />',\n    '<input aria-label={{labelText}} />',\n    '<input aria-labelledby=\"someIdValue\" />',\n    // https://github.com/ember-template-lint/ember-template-lint/issues/3388\n    '<input id=\"hello\" aria-label=\"hello\" />',\n    '<input id=\"hello\" aria-labelledby=\"someIdValue\" />',\n    '<div></div>',\n    '<Input id=\"foo\" />',\n    '{{input id=\"foo\"}}',\n    '<input ...attributes/>',\n    '<Input ...attributes />',\n    '<input id=\"label-input\" ...attributes>',\n    '<label>LabelText<textarea /></label>',\n    '<label><textarea />LabelText</label>',\n    '<label>LabelText<Textarea /></label>',\n    '<label><Textarea />LabelText</label>',\n    '<label>Label Text<div><textarea /></div></label>',\n    '<label>Text here<Textarea /></label>',\n    '<label>Text here {{textarea}}</label>',\n    '<textarea id=\"probablyHasLabel\" />',\n    '<textarea aria-label={{labelText}} />',\n    '<textarea aria-labelledby=\"someIdValue\" />',\n    '<Textarea id=\"foo\" />',\n    '{{textarea id=\"foo\"}}',\n    '<textarea ...attributes/>',\n    '<Textarea ...attributes />',\n    '<textarea id=\"label-input\" ...attributes />',\n    '<label>LabelText<select></select></label>',\n    '<label><select></select>LabelText</label>',\n    '<label>Label Text<div><select></select></div></label>',\n    '<select id=\"probablyHasLabel\"></select>',\n    '<select aria-label={{labelText}}></select>',\n    '<select aria-labelledby=\"someIdValue\"></select>',\n    '<select ...attributes></select>',\n    '<select id=\"label-input\" ...attributes ></select>',\n    '<input type=\"hidden\"/>',\n    '<Input type=\"hidden\" />',\n    '{{input type=\"hidden\"}}',\n    {\n      code: '<CustomLabel><input /></CustomLabel>',\n      options: [{ labelTags: ['CustomLabel'] }],\n    },\n    {\n      code: '<web-label><input /></web-label>',\n      options: [{ labelTags: [/web-label/] }],\n    },\n    {\n      code: '<input />',\n      options: [false],\n    },\n  ],\n  invalid: [\n    {\n      code: '<my-label><input /></my-label>',\n      output: null,\n      options: [{ labelTags: [/web-label/] }],\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<div><input /></div>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<input />',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<input title=\"some title value\" />',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<label><input></label>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<div>{{input}}</div>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<Input/>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<input aria-label=\"first label\" aria-labelledby=\"second label\">',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '<label>Input label<input aria-label=\"Custom label\"></label>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '<label>Input label<input id=\"foo\" aria-label=\"Custom label\"></label>',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '{{input type=\"button\"}}',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '{{input type=myType}}',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<Input type=\"button\"/>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<Input type={{myType}}/>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<textarea />',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<Textarea />',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<textarea aria-label=\"first label\" aria-labelledby=\"second label\" />',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n    {\n      code: '<select></select>',\n      output: null,\n      errors: [{ message: NO_LABEL }],\n    },\n    {\n      code: '<select aria-label=\"first label\" aria-labelledby=\"second label\" />',\n      output: null,\n      errors: [{ message: MULTIPLE_LABELS }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-input-type.js",
    "content": "const rule = require('../../../lib/rules/template-require-input-type');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MISSING = 'All `<input>` elements should have a `type` attribute';\nconst errInvalid = (value) => `\\`<input type=\"${value}\">\\` is not a valid input type`;\n\nconst validHbs = [\n  '<input type=\"text\" />',\n  '<input type=\"email\" />',\n  '<input type=\"checkbox\" />',\n  '<input type=\"submit\" />',\n  '<input type=\"datetime-local\" />',\n  '<input type=\"{{this.inputType}}\" />',\n  '<input type={{this.inputType}} />',\n  '<div />',\n  '<div type=\"foo\" />',\n  '<MyInput type=\"unknown\" />',\n  // Default (requireExplicit=false): missing `type` is allowed.\n  '<input />',\n  '<input name=\"email\" />',\n];\n\nconst invalidHbs = [\n  {\n    code: '<input type=\"\" />',\n    output: '<input type=\"text\" />',\n    errors: [{ message: errInvalid('') }],\n  },\n  {\n    code: '<input type=\"foo\" />',\n    output: '<input type=\"text\" />',\n    errors: [{ message: errInvalid('foo') }],\n  },\n  {\n    code: '<input type=\"TEXTY\" />',\n    output: '<input type=\"text\" />',\n    errors: [{ message: errInvalid('TEXTY') }],\n  },\n  // Valueless type attribute — per HTML spec resolves to the missing-value\n  // default (Text state), same runtime result as `type=\"\"`. Flag and autofix\n  // to `type=\"text\"`. (Output loses the pre-slash space because the\n  // valueless attr range ends at `type`; prettier will re-insert if needed.)\n  {\n    code: '<input type />',\n    output: '<input type=\"text\"/>',\n    errors: [{ message: errInvalid('') }],\n  },\n];\n\nconst requireExplicitInvalid = [\n  {\n    code: '<input />',\n    options: [{ requireExplicit: true }],\n    output: '<input type=\"text\" />',\n    errors: [{ message: ERROR_MISSING }],\n  },\n  {\n    code: '<input name=\"email\" />',\n    options: [{ requireExplicit: true }],\n    output: '<input type=\"text\" name=\"email\" />',\n    errors: [{ message: ERROR_MISSING }],\n  },\n  {\n    code: '<input   name=\"email\"   />',\n    options: [{ requireExplicit: true }],\n    output: '<input type=\"text\"   name=\"email\"   />',\n    errors: [{ message: ERROR_MISSING }],\n  },\n];\n\nconst requireExplicitValid = [\n  // With requireExplicit: an explicit known type satisfies the rule.\n  { code: '<input type=\"text\" />', options: [{ requireExplicit: true }] },\n  // Dynamic type also satisfies — we can't know the runtime value.\n  { code: '<input type={{this.inputType}} />', options: [{ requireExplicit: true }] },\n];\n\nconst gjsValid = validHbs.map((code) => `<template>${code}</template>`);\nconst gjsInvalid = invalidHbs.map(({ code, output, errors }) => ({\n  code: `<template>${code}</template>`,\n  output: `<template>${output}</template>`,\n  errors,\n}));\n\nconst gjsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\ngjsRuleTester.run('template-require-input-type', rule, {\n  valid: [\n    ...gjsValid,\n    ...requireExplicitValid.map(({ code, options }) => ({\n      code: `<template>${code}</template>`,\n      options,\n    })),\n    // Scope-shadowed `input` — the template's `<input>` refers to the local\n    // const binding (a component), not the native HTML element. The rule\n    // skips it via `isNativeElement`'s scope check.\n    `const input = 'foo';\n<template><input type=\"not-a-valid-type\" /></template>`,\n    `const input = 'foo';\n<template><input /></template>`,\n    // Block-param shadowing — `<Foo as |input|>` binds `input` inside the\n    // yield block. The inner `<input>` should resolve to the block-param,\n    // not the native tag.\n    `import Foo from 'whatever';\n<template><Foo as |input|><input type=\"not-a-valid-type\" /></Foo></template>`,\n  ],\n  invalid: [\n    ...gjsInvalid,\n    ...requireExplicitInvalid.map(({ code, options, output, errors }) => ({\n      code: `<template>${code}</template>`,\n      options,\n      output: `<template>${output}</template>`,\n      errors,\n    })),\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nhbsRuleTester.run('template-require-input-type', rule, {\n  valid: [...validHbs, ...requireExplicitValid],\n  invalid: [...invalidHbs, ...requireExplicitInvalid],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-lang-attribute.js",
    "content": "const rule = require('../../../lib/rules/template-require-lang-attribute');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ERROR_MESSAGE = 'The `<html>` element must have the `lang` attribute with a valid value';\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-lang-attribute', rule, {\n  valid: [\n    '<template><html lang=\"en\"></html></template>',\n    '<template><html lang=\"en-US\"></html></template>',\n    '<template><html lang=\"DE-BW\"></html></template>',\n    '<template><html lang=\"zh-Hant-HK\"></html></template>',\n    '<template><html lang=\"yue-Hans\"></html></template>',\n    '<template><html lang={{lang}}></html></template>',\n    {\n      code: '<template><html lang=\"de\"></html></template>',\n      options: [true],\n    },\n    {\n      code: '<template><html lang={{this.language}}></html></template>',\n      options: [true],\n    },\n    {\n      code: '<template><html lang=\"hurrah\"></html></template>',\n      options: [{ validateValues: false }],\n    },\n    {\n      code: '<template><html lang={{this.blah}}></html></template>',\n      options: [{ validateValues: false }],\n    },\n  ],\n\n  invalid: [\n    {\n      code: '<template><html></html></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html lang=\"\"></html></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html></html></template>',\n      output: null,\n      options: [true],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html lang=\"\"></html></template>',\n      output: null,\n      options: [true],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html lang=\"gibberish\"></html></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html lang=\"gibberish\"></html></template>',\n      output: null,\n      options: [{ validateValues: true }],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      // Invalid region subtag: \"xx\" is not a registered ISO 3166 / BCP 47\n      // region code. Prior to the country-codes port, the rule only\n      // validated the primary subtag and incorrectly accepted this value.\n      code: '<template><html lang=\"en-XX\"></html></template>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html></html></template>',\n      output: null,\n      options: [{ validateValues: false }],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<template><html lang=\"\"></html></template>',\n      output: null,\n      options: [{ validateValues: false }],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-lang-attribute', rule, {\n  valid: [\n    '<html lang=\"en\"></html>',\n    '<html lang=\"en-US\"></html>',\n    '<html lang=\"DE-BW\"></html>',\n    '<html lang=\"zh-Hant-HK\"></html>',\n    '<html lang=\"yue-Hans\"></html>',\n    '<html lang={{lang}}></html>',\n    {\n      code: '<html lang=\"de\"></html>',\n      options: [true],\n    },\n    {\n      code: '<html lang={{this.language}}></html>',\n      options: [true],\n    },\n    {\n      code: '<html lang=\"hurrah\"></html>',\n      options: [{ validateValues: false }],\n    },\n    {\n      code: '<html lang={{this.blah}}></html>',\n      options: [{ validateValues: false }],\n    },\n  ],\n  invalid: [\n    {\n      code: '<html></html>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html lang=\"\"></html>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html></html>',\n      output: null,\n      options: [true],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html lang=\"\"></html>',\n      output: null,\n      options: [true],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html lang=\"gibberish\"></html>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html lang=\"gibberish\"></html>',\n      output: null,\n      options: [{ validateValues: true }],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      // Invalid region subtag: \"xx\" is not a registered ISO 3166 / BCP 47\n      // region code. Prior to the country-codes port, the rule only\n      // validated the primary subtag and incorrectly accepted this value.\n      code: '<html lang=\"en-XX\"></html>',\n      output: null,\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html></html>',\n      output: null,\n      options: [{ validateValues: false }],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n    {\n      code: '<html lang=\"\"></html>',\n      output: null,\n      options: [{ validateValues: false }],\n      errors: [{ message: ERROR_MESSAGE }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-mandatory-role-attributes.js",
    "content": "const rule = require('../../../lib/rules/template-require-mandatory-role-attributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-mandatory-role-attributes', rule, {\n  valid: [\n    '<template><div /></template>',\n    '<template><div aria-disabled=\"true\" /></template>',\n    '<template><div role=\"complementary\" /></template>',\n    '<template><div role=\"combobox\" aria-expanded=\"false\" aria-controls=\"ctrlId\" /></template>',\n    '<template><div role=\"option\" aria-selected={{false}} /></template>',\n    '<template><FakeComponent /></template>',\n    '<template><FakeComponent role=\"fakerole\" /></template>',\n    '<template><CustomComponent role=\"checkbox\" aria-checked=\"false\" /></template>',\n    '<template><SomeComponent role={{this.role}} aria-notreal=\"bar\" /></template>',\n    '<template><OtherComponent @role={{@role}} aria-required={{this.required}} /></template>',\n    '<template><FakeElement aria-disabled=\"true\" /></template>',\n    '<template>{{some-component}}</template>',\n    '<template>{{some-component foo=\"true\"}}</template>',\n    '<template>{{some-component role=\"heading\" aria-level=\"2\"}}</template>',\n    '<template>{{foo-component role=\"button\"}}</template>',\n    '<template>{{foo-component role=\"unknown\"}}</template>',\n    '<template>{{foo-component role=role}}</template>',\n\n    // Semantic inputs supply required ARIA state natively. Exempt pairings\n    // are looked up via axobject-query's elementAXObjects + AXObjectRoles.\n\n    // checkbox/switch: aria-checked supplied via native `checked` state.\n    '<template><input type=\"checkbox\" role=\"switch\" /></template>',\n    '<template><input type=\"checkbox\" role=\"checkbox\" /></template>',\n    '<template><input type=\"radio\" role=\"radio\" /></template>',\n    '<template><input type=\"Checkbox\" role=\"switch\" /></template>',\n    '<template><input type=\"CHECKBOX\" role=\"switch\" /></template>',\n\n    // slider: aria-valuenow supplied via native `value` (axobject-query SliderRole).\n    '<template><input type=\"range\" role=\"slider\" /></template>',\n\n    // Classic Ember {{input type=... role=...}} helper renders a native\n    // <input>; same axobject-query lookup applies.\n    '<template>{{input type=\"checkbox\" role=\"switch\"}}</template>',\n    '<template>{{input type=\"Checkbox\" role=\"switch\"}}</template>',\n    '<template>{{input type=\"range\" role=\"slider\"}}</template>',\n\n    // Case-insensitive role matching — ARIA role tokens compare as ASCII-case-insensitive.\n    '<template><div role=\"COMBOBOX\" aria-expanded=\"false\" aria-controls=\"ctrl\" /></template>',\n    // Role fallback list — primary role's required attributes are satisfied.\n    '<template><div role=\"combobox listbox\" aria-expanded=\"false\" aria-controls=\"ctrl\" /></template>',\n    // Abstract roles (ARIA §5.3) are skipped per §4.1 fallback semantics —\n    // `widget` isn't an authoring role, so the UA walks past it to the next\n    // recognised token. Here `button` has no required attrs → valid.\n    '<template><div role=\"widget button\" /></template>',\n    // Abstract role followed by a concrete role that IS satisfied.\n    '<template><div role=\"command slider\" aria-valuenow=\"0\" /></template>',\n    // Unknown roles are skipped — rule only checks required attrs for known roles.\n    '<template><div role=\"foobar\" /></template>',\n  ],\n\n  invalid: [\n    {\n      code: '<template><div role=\"combobox\" aria-controls=\"someId\" /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-expanded is required by the role combobox',\n        },\n      ],\n    },\n    {\n      code: '<template><div role=\"option\"  /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-selected is required by the role option' }],\n    },\n    // Plain widget roles missing all required attrs — basic coverage that\n    // peer plugins (jsx-a11y / vue-a11y / angular-eslint) also flag.\n    {\n      code: '<template><div role=\"slider\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-valuenow is required by the role slider' }],\n    },\n    {\n      code: '<template><div role=\"checkbox\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n    {\n      code: '<template><CustomComponent role=\"checkbox\" aria-required=\"true\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n    {\n      code: '<template><SomeComponent role=\"scrollbar\" @aria-now={{this.valuenow}} aria-controls={{some-id}} /></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-valuenow is required by the role scrollbar',\n        },\n      ],\n    },\n    {\n      code: '<template>{{some-component role=\"heading\"}}</template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-level is required by the role heading' }],\n    },\n    {\n      code: '<template>{{foo role=\"slider\"}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-valuenow is required by the role slider',\n        },\n      ],\n    },\n    {\n      code: '<template>{{foo role=\"checkbox\"}}</template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n\n    // Undocumented {input type, role} pairings are NOT exempted.\n    {\n      code: '<template><input type=\"checkbox\" role=\"radio\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role radio' }],\n    },\n    // {{input}} helper with off-whitelist role is flagged too.\n    {\n      code: '<template>{{input type=\"text\" role=\"switch\"}}</template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n    {\n      code: '<template>{{input type=\"checkbox\" role=\"radio\"}}</template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role radio' }],\n    },\n    {\n      code: '<template><input type=\"radio\" role=\"switch\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n    {\n      code: '<template><input type=\"radio\" role=\"checkbox\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n    {\n      code: '<template><input type=\"text\" role=\"switch\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n    {\n      // No `type` attribute; defaults to text.\n      code: '<template><input role=\"switch\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n\n    // menuitemcheckbox / menuitemradio on <input> are NOT exempted —\n    // axobject-query's MenuItemCheckBoxRole / MenuItemRadioRole lists only\n    // an ARIA concept, no HTML concept for <input>. Flagged for missing\n    // aria-checked.\n    {\n      code: '<template><input type=\"checkbox\" role=\"menuitemcheckbox\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role menuitemcheckbox' }],\n    },\n    {\n      code: '<template><input type=\"radio\" role=\"menuitemradio\" /></template>',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role menuitemradio' }],\n    },\n    // Case-insensitive role matching — uppercase role missing required props is flagged.\n    {\n      code: '<template><div role=\"COMBOBOX\"></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attributes aria-controls, aria-expanded are required by the role combobox',\n        },\n      ],\n    },\n    // Role-fallback list: when the primary role is missing required props, flag it.\n    {\n      code: '<template><div role=\"combobox listbox\"></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attributes aria-controls, aria-expanded are required by the role combobox',\n        },\n      ],\n    },\n    // Abstract role (`widget`) followed by a concrete role that's missing\n    // required attrs — UA skips the abstract, lands on `slider`, which\n    // requires aria-valuenow.\n    {\n      code: '<template><div role=\"widget slider\"></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-valuenow is required by the role slider',\n        },\n      ],\n    },\n    // Strict-mode {{input}} is a scope binding, not Ember's classic helper\n    // (which doesn't exist as a strict-mode export from @ember/component).\n    // The semantic-role exemption must NOT apply — we can't prove the\n    // imported identifier renders a native <input>. Flag the missing ARIA.\n    {\n      filename: 'component.gjs',\n      code: '<template>{{input type=\"checkbox\" role=\"switch\"}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-checked is required by the role switch',\n        },\n      ],\n    },\n    {\n      filename: 'component.gts',\n      code: '<template>{{input type=\"range\" role=\"slider\"}}</template>',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-valuenow is required by the role slider',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-mandatory-role-attributes', rule, {\n  valid: [\n    '<div />',\n    '<div aria-disabled=\"true\" />',\n    '<div role=\"complementary\" />',\n    '<div role=\"combobox\" aria-expanded=\"false\" aria-controls=\"ctrlId\" />',\n    '<div role=\"option\" aria-selected={{false}} />',\n    '<FakeComponent />',\n    '<FakeComponent role=\"fakerole\" />',\n    '<CustomComponent role=\"checkbox\" aria-checked=\"false\" />',\n    '<SomeComponent role={{this.role}} aria-notreal=\"bar\" />',\n    '<OtherComponent @role={{@role}} aria-required={{this.required}} />',\n    '<FakeElement aria-disabled=\"true\" />',\n    '{{some-component}}',\n    '{{some-component foo=\"true\"}}',\n    '{{some-component role=\"heading\" aria-level=\"2\"}}',\n    '{{foo-component role=\"button\"}}',\n    '{{foo-component role=\"unknown\"}}',\n    '{{foo-component role=role}}',\n\n    // Semantic inputs supply required ARIA state natively (via axobject-query\n    // elementAXObjects lookup).\n    '<input type=\"checkbox\" role=\"switch\" />',\n    '<input type=\"radio\" role=\"radio\" />',\n    '<input type=\"checkbox\" role=\"checkbox\" />',\n    '<input type=\"Checkbox\" role=\"switch\" />',\n    '<input type=\"CHECKBOX\" role=\"switch\" />',\n    '<input type=\"range\" role=\"slider\" />',\n\n    // Classic Ember {{input}} helper renders a native <input>; same lookup.\n    '{{input type=\"checkbox\" role=\"switch\"}}',\n    '{{input type=\"Checkbox\" role=\"switch\"}}',\n    '{{input type=\"range\" role=\"slider\"}}',\n  ],\n  invalid: [\n    {\n      code: '<div role=\"combobox\" aria-controls=\"someId\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-expanded is required by the role combobox' }],\n    },\n    {\n      code: '<div role=\"option\"  />',\n      output: null,\n      errors: [{ message: 'The attribute aria-selected is required by the role option' }],\n    },\n    {\n      code: '<CustomComponent role=\"checkbox\" aria-required=\"true\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n    {\n      code: '<SomeComponent role=\"scrollbar\" @aria-now={{this.valuenow}} aria-controls={{some-id}} />',\n      output: null,\n      errors: [{ message: 'The attribute aria-valuenow is required by the role scrollbar' }],\n    },\n    {\n      code: '{{some-component role=\"heading\"}}',\n      output: null,\n      errors: [{ message: 'The attribute aria-level is required by the role heading' }],\n    },\n    {\n      code: '{{foo role=\"slider\"}}',\n      output: null,\n      errors: [\n        {\n          message: 'The attribute aria-valuenow is required by the role slider',\n        },\n      ],\n    },\n    {\n      code: '{{foo role=\"checkbox\"}}',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n\n    // Undocumented {input type, role} pairings are NOT exempted.\n    {\n      code: '<input type=\"checkbox\" role=\"radio\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role radio' }],\n    },\n    {\n      code: '<input type=\"radio\" role=\"switch\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n    {\n      code: '<input type=\"radio\" role=\"checkbox\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role checkbox' }],\n    },\n    {\n      code: '<input type=\"text\" role=\"switch\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n    {\n      // No `type` attribute; defaults to text.\n      code: '<input role=\"switch\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n\n    // {{input}} helper with off-whitelist role is flagged too.\n    {\n      code: '{{input type=\"text\" role=\"switch\"}}',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role switch' }],\n    },\n    {\n      code: '{{input type=\"checkbox\" role=\"radio\"}}',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role radio' }],\n    },\n\n    // menuitemcheckbox / menuitemradio on <input> are NOT exempted.\n    {\n      code: '<input type=\"checkbox\" role=\"menuitemcheckbox\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role menuitemcheckbox' }],\n    },\n    {\n      code: '<input type=\"radio\" role=\"menuitemradio\" />',\n      output: null,\n      errors: [{ message: 'The attribute aria-checked is required by the role menuitemradio' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-media-caption.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-require-media-caption');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-media-caption', rule, {\n  valid: [\n    `<template>\n      <video>\n        <track kind=\"captions\" src=\"captions.vtt\" />\n      </video>\n    </template>`,\n    `<template>\n      <audio>\n        <track kind=\"captions\" src=\"captions.vtt\" />\n      </audio>\n    </template>`,\n    `<template>\n      <div>No media elements</div>\n    </template>`,\n\n    '<template><video><track kind=\"captions\" /></video></template>',\n    // HTML enumerated attribute values are case-insensitive, so \"Captions\" is\n    // the same as \"captions\" for the track element. Matches jsx-a11y/vue-a11y.\n    '<template><video><track kind=\"Captions\" /></video></template>',\n    '<template><video><track kind=\"CAPTIONS\" /></video></template>',\n    '<template><audio muted=\"true\"></audio></template>',\n    '<template><video muted></video></template>',\n    '<template><audio muted={{this.muted}}></audio></template>',\n    '<template><video muted=\"{{isMuted}}\"><source src=\"movie.mp4\" /></video></template>',\n    '<template><audio muted=\"{{this.isMuted}}\"></audio></template>',\n    '<template><video><track kind=\"captions\" /><track kind=\"descriptions\" /></video></template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <video src=\"movie.mp4\"></video>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <audio src=\"audio.mp3\"></audio>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <video>\n          <track kind=\"subtitles\" src=\"subs.vtt\" />\n        </video>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><video></video></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<template><audio><track /></audio></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<template><video><track kind=\"subtitles\" /></video></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<template><audio muted=\"false\"></audio></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<template><audio muted=\"false\"><track kind=\"descriptions\" /></audio></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<template><video muted=false></video></template>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-media-caption', rule, {\n  valid: [\n    '<video><track kind=\"captions\" /></video>',\n    '<audio muted=\"true\"></audio>',\n    '<video muted></video>',\n    '<audio muted={{this.muted}}></audio>',\n    '<video muted=\"{{isMuted}}\"><source src=\"movie.mp4\" /></video>',\n    '<audio muted=\"{{this.isMuted}}\"></audio>',\n    '<video><track kind=\"captions\" /><track kind=\"descriptions\" /></video>',\n  ],\n  invalid: [\n    {\n      code: '<video></video>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<audio><track /></audio>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<video><track kind=\"subtitles\" /></video>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<audio muted=\"false\"></audio>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<audio muted=\"false\"><track kind=\"descriptions\" /></audio>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n    {\n      code: '<video muted=false></video>',\n      output: null,\n      errors: [\n        {\n          message: 'Media elements such as <audio> and <video> must have a <track> for captions.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-presentational-children.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-require-presentational-children');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-presentational-children', rule, {\n  valid: [\n    '<template><button></button></template>',\n    '<template><div></div></template>',\n    '<template><li role=\"tab\">Tab title</li></template>',\n    '<template><li role=\"tab\"><h3 role=\"presentation\">Tab Title</h3></li></template>',\n    '<template><div role=\"button\"><div><span></span></div></div></template>',\n    '<template><span role=\"checkbox\"/></template>',\n    '<template><div role=\"article\"><h2>Hello</h2></div></template>',\n    `<template>\n    <ul role=\"tablist\">\n      <li role=\"presentation\">\n        <a role=\"tab\" href=\"#\">Tab 1</a>\n      </li>\n    </ul>\n    </template>`,\n    `<template>\n    <svg role=\"img\">\n      <title>Title here</title>\n      <circle cx=\"10\" cy=\"10\" r=\"10\"></circle>\n    </svg></template>`,\n    // SKIPPED_TAGS: <svg> is always skipped, even when its role has\n    // childrenPresentational (e.g. graphics-symbol via Graphics-ARIA).\n    `<template>\n    <svg role=\"graphics-symbol\">\n      <circle cx=\"10\" cy=\"10\" r=\"10\"></circle>\n    </svg></template>`,\n    `<template>\n      <MyButton role=\"tab\">\n        <:default>Button text</:default>\n      </MyButton>\n    </template>`,\n    {\n      code: '<template><button><div>item1</div><custom-element>item2</custom-element></button></template>',\n      options: [{ additionalNonSemanticTags: ['custom-element'] }],\n    },\n  ],\n\n  invalid: [\n    {\n      code: '<template><div role=\"button\"><h2>Test</h2></div></template>',\n      output: null,\n      errors: [\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <h2>',\n        },\n      ],\n    },\n    {\n      code: '<template><div role=\"button\"><h2 role=\"presentation\"><img /></h2></div></template>',\n      output: null,\n      errors: [\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <img>',\n        },\n      ],\n    },\n    {\n      code: '<template><div role=\"button\"><h2 role=\"presentation\"><button>Test <img/></button></h2></div></template>',\n      output: null,\n      errors: [\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <button>',\n        },\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <img>',\n        },\n      ],\n    },\n    // doc-pagebreak: DPUB-ARIA role with childrenPresentational.\n    {\n      code: '<template><div role=\"doc-pagebreak\"><h2>pg</h2></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '<div> has a role of doc-pagebreak, it cannot have semantic descendants like <h2>',\n        },\n      ],\n    },\n    // graphics-symbol: Graphics-ARIA role with childrenPresentational; flags on non-svg host.\n    {\n      code: '<template><div role=\"graphics-symbol\"><text>X</text></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            '<div> has a role of graphics-symbol, it cannot have semantic descendants like <text>',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-presentational-children', rule, {\n  valid: [\n    '<button></button>',\n    '<div></div>',\n    '<li role=\"tab\">Tab title</li>',\n    '<li role=\"tab\"><h3 role=\"presentation\">Tab Title</h3></li>',\n    '<div role=\"button\"><div><span></span></div></div>',\n    '<span role=\"checkbox\"/>',\n    '<div role=\"article\"><h2>Hello</h2></div>',\n    `\n    <ul role=\"tablist\">\n      <li role=\"presentation\">\n        <a role=\"tab\" href=\"#\">Tab 1</a>\n      </li>\n    </ul>\n    `,\n    `\n    <svg role=\"img\">\n      <title>Title here</title>\n      <circle cx=\"10\" cy=\"10\" r=\"10\"></circle>\n    </svg>`,\n    // SKIPPED_TAGS: <svg> is always skipped, even when its role has\n    // childrenPresentational (e.g. graphics-symbol via Graphics-ARIA).\n    `\n    <svg role=\"graphics-symbol\">\n      <circle cx=\"10\" cy=\"10\" r=\"10\"></circle>\n    </svg>`,\n    `\n      <MyButton role=\"tab\">\n        <:default>Button text</:default>\n      </MyButton>\n    `,\n    {\n      code: '<button><div>item1</div><custom-element>item2</custom-element></button>',\n      options: [{ additionalNonSemanticTags: ['custom-element'] }],\n    },\n  ],\n  invalid: [\n    {\n      code: '<div role=\"button\"><h2>Test</h2></div>',\n      output: null,\n      errors: [\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <h2>',\n        },\n      ],\n    },\n    {\n      code: '<div role=\"button\"><h2 role=\"presentation\"><img /></h2></div>',\n      output: null,\n      errors: [\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <img>',\n        },\n      ],\n    },\n    {\n      code: '<div role=\"button\"><h2 role=\"presentation\"><button>Test <img/></button></h2></div>',\n      output: null,\n      errors: [\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <button>',\n        },\n        {\n          message: '<div> has a role of button, it cannot have semantic descendants like <img>',\n        },\n      ],\n    },\n    // doc-pagebreak: DPUB-ARIA role with childrenPresentational.\n    {\n      code: '<div role=\"doc-pagebreak\"><h2>pg</h2></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            '<div> has a role of doc-pagebreak, it cannot have semantic descendants like <h2>',\n        },\n      ],\n    },\n    // graphics-symbol: Graphics-ARIA role with childrenPresentational; flags on non-svg host.\n    {\n      code: '<div role=\"graphics-symbol\"><text>X</text></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            '<div> has a role of graphics-symbol, it cannot have semantic descendants like <text>',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-splattributes.js",
    "content": "const rule = require('../../../lib/rules/template-require-splattributes');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-splattributes', rule, {\n  valid: [\n    '<template><div ...attributes></div></template>',\n    '<template><Foo ...attributes></Foo></template>',\n    '<template><div ...attributes /></template>',\n    '<template><div><Foo ...attributes /></div></template>',\n    '<template><div ...attributes></div><div></div></template>',\n\n    '<template><div></div><div ...attributes></div><div></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div></div></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The root element in this template should use `...attributes`',\n        },\n      ],\n    },\n    {\n      code: '<template><Foo></Foo></template>',\n      output: null,\n      errors: [\n        {\n          message: 'The root element in this template should use `...attributes`',\n        },\n      ],\n    },\n\n    {\n      code: '<template><div></div><div></div></template>',\n      output: null,\n      errors: [{ message: 'At least one element in this template should use `...attributes`' }],\n    },\n    {\n      code: `<template><div/>\n\n</template>`,\n      output: null,\n      errors: [{ message: 'The root element in this template should use `...attributes`' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-splattributes', rule, {\n  valid: [\n    '<div ...attributes></div>',\n    '<Foo ...attributes></Foo>',\n    '<div ...attributes />',\n    '<div><Foo ...attributes /></div>',\n    '<div ...attributes></div><div></div>',\n    '<div></div><div ...attributes></div><div></div>',\n  ],\n  invalid: [\n    {\n      code: '<div></div>',\n      output: null,\n      errors: [{ message: 'The root element in this template should use `...attributes`' }],\n    },\n    {\n      code: '<Foo></Foo>',\n      output: null,\n      errors: [{ message: 'The root element in this template should use `...attributes`' }],\n    },\n    {\n      code: '<div></div><div></div>',\n      output: null,\n      errors: [{ message: 'At least one element in this template should use `...attributes`' }],\n    },\n    {\n      code: `<div/>\n\n`,\n      output: null,\n      errors: [{ message: 'The root element in this template should use `...attributes`' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-strict-mode.js",
    "content": "const rule = require('../../../lib/rules/template-require-strict-mode');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-strict-mode', rule, {\n  valid: [\n    {\n      filename: 'hello.gjs',\n      code: '<template>hello</template>',\n    },\n    {\n      filename: 'hello.gts',\n      code: '<template>hello</template>',\n    },\n    {\n      filename: 'hello.gjs',\n      code: `import Component from '@glimmer/component';\nexport default class HelloComponent extends Component {\n  <template>hello</template>\n}`,\n    },\n  ],\n  invalid: [\n    {\n      filename: 'hello.hbs',\n      code: '<template><div>hello</div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Templates are required to be in strict mode. Consider refactoring to template tag format.',\n        },\n      ],\n    },\n    {\n      filename: 'hello.hbs',\n      code: `<template><div>\n  hello\n</div></template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Templates are required to be in strict mode. Consider refactoring to template tag format.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-strict-mode', rule, {\n  valid: [],\n  invalid: [\n    {\n      code: '<div>hello</div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Templates are required to be in strict mode. Consider refactoring to template tag format.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-valid-alt-text.js",
    "content": "const rule = require('../../../lib/rules/template-require-valid-alt-text');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-valid-alt-text', rule, {\n  valid: [\n    '<template><img alt=\"A cat\" src=\"/cat.jpg\" /></template>',\n    '<template><img alt=\"Company branding\" src=\"/logo.png\" /></template>',\n    '<template><img alt=\"\" src=\"/decorative.png\" /></template>',\n    '<template><img hidden alt=\"\" /></template>',\n    // Whitespace-only alt — pin our current behavior. Peer plugins\n    // (jsx-a11y) accept this; we don't trim before considering \"empty alt\".\n    '<template><img alt=\" \" /></template>',\n\n    '<template><img alt=\"hullo\"></template>',\n    '<template><img alt={{foo}}></template>',\n    '<template><img alt=\"blah {{derp}}\"></template>',\n    '<template><img aria-hidden=\"true\"></template>',\n    '<template><img hidden></template>',\n    '<template><img alt=\"\" role=\"none\" src=\"zoey.jpg\"></template>',\n    '<template><img alt=\"\" role=\"presentation\" src=\"zoey.jpg\"></template>',\n    '<template><img alt=\"a stylized graphic of a female hamster\" src=\"zoey.jpg\"></template>',\n    '<template><img alt=\"some-alt-name\"></template>',\n    '<template><img alt=\"name {{picture}}\"></template>',\n    '<template><img alt=\"{{picture}}\"></template>',\n    '<template><img alt=\"\"></template>',\n    '<template><img alt=\"\" src=\"zoey.jpg\"></template>',\n    '<template><img alt=\"\" role=\"none\"></template>',\n    '<template><img alt=\"\" role=\"presentation\"></template>',\n    '<template><img alt></template>',\n    '<template><img alt role=\"none\"></template>',\n    '<template><img alt role=\"presentation\"></template>',\n    '<template><img alt src=\"zoey.jpg\"></template>',\n    '<template><img alt=\"logout\"></template>',\n    '<template><img alt=\"photography\"></template>',\n    '<template><img alt=\"picturesque\"></template>',\n    '<template><img alt=\"pilgrimage\"></template>',\n    '<template><img alt=\"spacers\"></template>',\n    '<template><img ...attributes></template>',\n    '<template><input type=\"image\" alt=\"some-alt\"></template>',\n    '<template><input type=\"image\" aria-labelledby=\"some-alt\"></template>',\n    '<template><input type=\"image\" aria-label=\"some-alt\"></template>',\n    '<template><input type=\"image\" hidden></template>',\n    '<template><input type=\"image\" aria-hidden=\"true\"></template>',\n    '<template><object title=\"some-alt\"></object></template>',\n    '<template><object role=\"presentation\"></object></template>',\n    '<template><object role=\"none\"></object></template>',\n    '<template><object hidden></object></template>',\n    '<template><object aria-hidden=\"true\"></object></template>',\n    '<template><object aria-labelledby=\"some-alt\"></object></template>',\n    '<template><object aria-label=\"some-alt\"></object></template>',\n    '<template><object>some text</object></template>',\n    '<template><area alt=\"some-alt\"></template>',\n    '<template><area hidden></template>',\n    '<template><area aria-hidden=\"true\"></template>',\n    '<template><area aria-labelledby=\"some-alt\"></template>',\n    '<template><area aria-label=\"some-alt\"></template>',\n    '<template><img role={{unless this.altText \"presentation\"}} alt={{this.altText}}></template>',\n    // Mustache-string-literal forms resolve to their static value — a\n    // non-empty literal supplies an accessible name the same as a text node.\n    '<template><input type=\"image\" aria-label={{\"valid\"}} /></template>',\n  ],\n  invalid: [\n    // Empty-string aria-label / aria-labelledby / alt provides no accessible\n    // name, so these must flag.\n    {\n      code: '<template><input type=\"image\" aria-label=\"\" /></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><input type=\"image\" aria-labelledby=\"\" /></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><input type=\"image\" alt=\"\" /></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><object aria-label=\"\"></object></template>',\n      output: null,\n      errors: [{ messageId: 'objectMissing' }],\n    },\n    {\n      code: '<template><object aria-labelledby=\"\"></object></template>',\n      output: null,\n      errors: [{ messageId: 'objectMissing' }],\n    },\n    {\n      code: '<template><object title=\"\"></object></template>',\n      output: null,\n      errors: [{ messageId: 'objectMissing' }],\n    },\n    {\n      code: '<template><area aria-label=\"\"></template>',\n      output: null,\n      errors: [{ messageId: 'areaMissing' }],\n    },\n    {\n      code: '<template><area aria-labelledby=\"\"></template>',\n      output: null,\n      errors: [{ messageId: 'areaMissing' }],\n    },\n    {\n      code: '<template><area alt=\"\"></template>',\n      output: null,\n      errors: [{ messageId: 'areaMissing' }],\n    },\n    // Whitespace-only values are not valid accessible names per ACCNAME 1.2 §4.3.2 step 2D.\n    {\n      code: '<template><input type=\"image\" aria-label=\" \" /></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><object aria-labelledby=\"\\n\\t\" ></object></template>',\n      output: null,\n      errors: [{ messageId: 'objectMissing' }],\n    },\n    // <area>: title is NOT one of the accepted fallbacks per ACCNAME.\n    // Only alt / aria-label / aria-labelledby contribute. A whitespace-only\n    // aria-label should be flagged.\n    {\n      code: '<template><area href=\"/\" aria-label=\" \" /></template>',\n      output: null,\n      errors: [{ messageId: 'areaMissing' }],\n    },\n    // Mustache-string-literal forms that resolve to an empty string are\n    // treated the same as the text-node empty value — no accessible name.\n    {\n      code: '<template><input type=\"image\" aria-label={{\"\"}} /></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><input type=\"image\" aria-label=\"{{\"\"}}\" /></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><img src=\"/test.jpg\" /></template>',\n      output: null,\n      errors: [{ messageId: 'imgMissing' }],\n    },\n    {\n      code: '<template><img alt=\"image of a cat\" src=\"/cat.jpg\" /></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n    {\n      code: '<template><img alt=\"photo of sunset\" src=\"/sunset.jpg\" /></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n\n    {\n      code: '<template><img></template>',\n      output: null,\n      errors: [{ messageId: 'imgMissing' }],\n    },\n    {\n      code: '<template><img src=\"zoey.jpg\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgMissing' }],\n    },\n    {\n      code: '<template><img alt=\"path/to/zoey.jpg\" src=\"path/to/zoey.jpg\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgAltEqualsSrc' }],\n    },\n    {\n      code: '<template><input type=\"image\"></template>',\n      output: null,\n      errors: [{ messageId: 'inputImage' }],\n    },\n    {\n      code: '<template><object></object></template>',\n      output: null,\n      errors: [{ messageId: 'objectMissing' }],\n    },\n    {\n      code: '<template><object /></template>',\n      output: null,\n      errors: [{ messageId: 'objectMissing' }],\n    },\n    {\n      code: '<template><area></template>',\n      output: null,\n      errors: [{ messageId: 'areaMissing' }],\n    },\n    {\n      code: '<template><img alt=\"picture\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n    {\n      code: '<template><img alt=\"photo\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n    {\n      code: '<template><img alt=\"image\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n    {\n      code: '<template><img alt=\"  IMAGE \"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n    {\n      code: '<template><img alt=\"  IMAGE {{picture}} {{word}} \"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRedundant' }],\n    },\n    {\n      code: '<template><img alt=\"52\" src=\"b52.jpg\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgNumericAlt' }],\n    },\n    {\n      code: '<template><img alt=\"not-null-alt\" src=\"zoey.jpg\" role=\"none\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRolePresentation' }],\n    },\n    {\n      code: '<template><img alt=\"not-null-alt\" src=\"zoey.jpg\" role=\"presentation\"></template>',\n      output: null,\n      errors: [{ messageId: 'imgRolePresentation' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-valid-alt-text', rule, {\n  valid: [\n    '<img alt=\"hullo\">',\n    '<img alt={{foo}}>',\n    '<img alt=\"blah {{derp}}\">',\n    '<img aria-hidden=\"true\">',\n    '<img hidden>',\n    '<img alt=\"\" role=\"none\" src=\"zoey.jpg\">',\n    '<img alt=\"\" role=\"presentation\" src=\"zoey.jpg\">',\n    '<img alt=\"a stylized graphic of a female hamster\" src=\"zoey.jpg\">',\n    '<img alt=\"some-alt-name\">',\n    '<img alt=\"name {{picture}}\">',\n    '<img alt=\"{{picture}}\">',\n    '<img alt=\"\">',\n    '<img alt=\"\" src=\"zoey.jpg\">',\n    '<img alt=\"\" role=\"none\">',\n    '<img alt=\"\" role=\"presentation\">',\n    '<img alt>',\n    '<img alt role=\"none\">',\n    '<img alt role=\"presentation\">',\n    '<img alt src=\"zoey.jpg\">',\n    '<img alt=\"logout\">',\n    '<img alt=\"photography\">',\n    '<img alt=\"picturesque\">',\n    '<img alt=\"pilgrimage\">',\n    '<img alt=\"spacers\">',\n    '<img ...attributes>',\n    '<input type=\"image\" alt=\"some-alt\">',\n    '<input type=\"image\" aria-labelledby=\"some-alt\">',\n    '<input type=\"image\" aria-label=\"some-alt\">',\n    '<input type=\"image\" hidden>',\n    '<input type=\"image\" aria-hidden=\"true\">',\n    '<object title=\"some-alt\"></object>',\n    '<object role=\"presentation\"></object>',\n    '<object role=\"none\"></object>',\n    '<object hidden></object>',\n    '<object aria-hidden=\"true\"></object>',\n    '<object aria-labelledby=\"some-alt\"></object>',\n    '<object aria-label=\"some-alt\"></object>',\n    '<object>some text</object>',\n    '<area alt=\"some-alt\">',\n    '<area hidden>',\n    '<area aria-hidden=\"true\">',\n    '<area aria-labelledby=\"some-alt\">',\n    '<area aria-label=\"some-alt\">',\n    '<img role={{unless this.altText \"presentation\"}} alt={{this.altText}}>',\n  ],\n  invalid: [\n    {\n      code: '<img>',\n      output: null,\n      errors: [{ message: 'All `<img>` tags must have an alt attribute' }],\n    },\n    {\n      code: '<img src=\"zoey.jpg\">',\n      output: null,\n      errors: [{ message: 'All `<img>` tags must have an alt attribute' }],\n    },\n    {\n      code: '<img alt=\"path/to/zoey.jpg\" src=\"path/to/zoey.jpg\">',\n      output: null,\n      errors: [{ message: 'The alt text must not be the same as the image source' }],\n    },\n    {\n      code: '<input type=\"image\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'All <input> elements with type=\"image\" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',\n        },\n      ],\n    },\n    {\n      code: '<object></object>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby attributes.',\n        },\n      ],\n    },\n    {\n      code: '<object />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby attributes.',\n        },\n      ],\n    },\n    {\n      code: '<area>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',\n        },\n      ],\n    },\n    // HBS parity: empty / whitespace-only accessible-name fallbacks\n    // should be flagged, mirroring the GTS cases above.\n    {\n      code: '<input type=\"image\" aria-label=\" \" />',\n      output: null,\n      errors: [\n        {\n          message:\n            'All <input> elements with type=\"image\" must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',\n        },\n      ],\n    },\n    {\n      code: '<object aria-labelledby=\"\\n\\t\" ></object>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Embedded <object> elements must have alternative text by providing inner text, aria-label or aria-labelledby attributes.',\n        },\n      ],\n    },\n    {\n      code: '<area href=\"/\" aria-label=\" \" />',\n      output: null,\n      errors: [\n        {\n          message:\n            'Each area of an image map must have a text alternative through the `alt`, `aria-label`, or `aria-labelledby` attribute.',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"picture\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"photo\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"image\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"  IMAGE \">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"  IMAGE {{picture}} {{word}} \">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Invalid alt attribute. Words such as `image`, `photo,` or `picture` are already announced by screen readers.',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"52\" src=\"b52.jpg\">',\n      output: null,\n      errors: [{ message: 'A number is not valid alt text' }],\n    },\n    {\n      code: '<img alt=\"not-null-alt\" src=\"zoey.jpg\" role=\"none\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'The `alt` attribute should be empty if `<img>` has `role` of `none` or `presentation`',\n        },\n      ],\n    },\n    {\n      code: '<img alt=\"not-null-alt\" src=\"zoey.jpg\" role=\"presentation\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'The `alt` attribute should be empty if `<img>` has `role` of `none` or `presentation`',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-valid-form-groups.js",
    "content": "const rule = require('../../../lib/rules/template-require-valid-form-groups');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-valid-form-groups', rule, {\n  valid: [\n    `<template>\n      <fieldset>\n        <legend>Preferred Mascot Version</legend>\n        <div>\n          <label for=\"radio-001\">Chicago Zoey</label>\n          <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\" />\n        </div>\n        <div>\n          <label for=\"radio-002\">Office Hours Tomster</label>\n          <input\n            id=\"radio-002\"\n            type=\"radio\"\n            name=\"prefMascot-OfficeHoursTomster\"\n            value=\"office hours tomster\"\n          />\n        </div>\n        <div>\n          <label for=\"radio-003\">A11y Zoey</label>\n          <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\" />\n        </div>\n      </fieldset>\n    </template>`,\n    `<template>\n      <div role=\"group\" aria-labelledby=\"preferred-mascot-heading\">\n        <div id=\"preferred-mascot-heading\">Preferred Mascot Version</div>\n        <label for=\"radio-001\">Chicago Zoey</label>\n        <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\" />\n        <label for=\"radio-002\">Office Hours Tomster</label>\n        <input\n          id=\"radio-002\"\n          type=\"radio\"\n          name=\"prefMascot-OfficeHoursTomster\"\n          value=\"office hours tomster\"\n        />\n        <label for=\"radio-003\">A11y Zoey</label>\n        <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\" />\n      </div>\n    </template>`,\n    `<template>\n      <div>\n        <label for=\"radio-001\">Chicago Zoey</label>\n        <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\" />\n      </div>\n    </template>`,\n\n    `<template><fieldset>\n      <legend>Preferred Mascot Version</legend>\n      <div>\n        <label for=\"radio-001\">Chicago Zoey</label>\n        <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\">\n      </div>\n      <div>\n        <label for=\"radio-002\">Office Hours Tomster</label>\n        <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\">\n      </div>\n      <div>\n        <label for=\"radio-003\">A11y Zoey</label>\n        <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\">\n      </div>\n    </fieldset></template>`,\n    `<template><div role=\"group\" aria-labelledby=\"preferred-mascot-heading\">\n      <div id=\"preferred-mascot-heading\">Preferred Mascot Version</div>\n      <label for=\"radio-001\">Chicago Zoey</label>\n      <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\">\n      <label for=\"radio-002\">Office Hours Tomster</label>\n      <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\">\n      <label for=\"radio-003\">A11y Zoey</label>\n      <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\">\n    </div></template>`,\n    `<template><div>\n      <label for=\"radio-001\">Chicago Zoey</label>\n      <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\">\n    </div></template>`,\n  ],\n  invalid: [\n    {\n      code: '<template><div><input name=\"a1\">Chicago Zoey<input name=\"a2\">Chicago Tom</div></template>',\n      output: null,\n      errors: [{ messageId: 'requireValidFormGroups' }, { messageId: 'requireValidFormGroups' }],\n    },\n    {\n      code: '<template><div><input id=\"prefMascot-Zoey\"><label for=\"prefMascot-Zoey\" /><input id=\"prefMascot-tom\"><label for=\"prefMascot-tom\" /></div></template>',\n      output: null,\n      errors: [{ messageId: 'requireValidFormGroups' }, { messageId: 'requireValidFormGroups' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-valid-form-groups', rule, {\n  valid: [\n    `<fieldset>\n      <legend>Preferred Mascot Version</legend>\n      <div>\n        <label for=\"radio-001\">Chicago Zoey</label>\n        <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\">\n      </div>\n      <div>\n        <label for=\"radio-002\">Office Hours Tomster</label>\n        <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\">\n      </div>\n      <div>\n        <label for=\"radio-003\">A11y Zoey</label>\n        <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\">\n      </div>\n    </fieldset>`,\n    `<div role=\"group\" aria-labelledby=\"preferred-mascot-heading\">\n      <div id=\"preferred-mascot-heading\">Preferred Mascot Version</div>\n      <label for=\"radio-001\">Chicago Zoey</label>\n      <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\">\n      <label for=\"radio-002\">Office Hours Tomster</label>\n      <input id=\"radio-002\" type=\"radio\" name=\"prefMascot-OfficeHoursTomster\" value=\"office hours tomster\">\n      <label for=\"radio-003\">A11y Zoey</label>\n      <input id=\"radio-003\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"a11y zoey\">\n    </div>`,\n    `<div>\n      <label for=\"radio-001\">Chicago Zoey</label>\n      <input id=\"radio-001\" type=\"radio\" name=\"prefMascot-Zoey\" value=\"chicago zoey\">\n    </div>`,\n  ],\n  invalid: [\n    {\n      code: '<div><input name=\"a1\">Chicago Zoey<input name=\"a2\">Chicago Tom</div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Grouped form controls should have appropriate semantics such as fieldset and legend or WAI-ARIA labels',\n        },\n        {\n          message:\n            'Grouped form controls should have appropriate semantics such as fieldset and legend or WAI-ARIA labels',\n        },\n      ],\n    },\n    {\n      code: '<div><input id=\"prefMascot-Zoey\"><label for=\"prefMascot-Zoey\" /><input id=\"prefMascot-tom\"><label for=\"prefMascot-tom\" /></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Grouped form controls should have appropriate semantics such as fieldset and legend or WAI-ARIA labels',\n        },\n        {\n          message:\n            'Grouped form controls should have appropriate semantics such as fieldset and legend or WAI-ARIA labels',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-require-valid-named-block-naming-format.js",
    "content": "const rule = require('../../../lib/rules/template-require-valid-named-block-naming-format');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-require-valid-named-block-naming-format', rule, {\n  valid: [\n    '<template>{{yield}}</template>',\n    '<template>{{yield to=\"fooBar\"}}</template>',\n    '<template>{{has-block}}</template>',\n    '<template>{{has-block \"fooBar\"}}</template>',\n    {\n      code: '<template>{{yield to=\"foo-bar\"}}</template>',\n      options: [false],\n    },\n    {\n      code: '<template>{{yield to=\"foo-bar\"}}</template>',\n      output: null,\n      options: ['kebab-case'],\n    },\n    {\n      code: '<template>{{has-block \"foo-bar\"}}</template>',\n      output: null,\n      options: ['kebab-case'],\n    },\n\n    '<template>{{if (has-block)}}</template>',\n    '<template>{{if (has-block \"fooBar\")}}</template>',\n    '<template>{{has-block-params}}</template>',\n    '<template>{{has-block-params \"fooBar\"}}</template>',\n    '<template>{{if (has-block-params)}}</template>',\n    '<template>{{if (has-block-params \"fooBar\")}}</template>',\n    '<template>camelCase</template>',\n    '<template>kebab-case</template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{yield to=\"foo-bar\"}}</template>',\n      output: '<template>{{yield to=\"fooBar\"}}</template>',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '<template>{{has-block \"foo-bar\"}}</template>',\n      output: '<template>{{has-block \"fooBar\"}}</template>',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '<template>{{yield to=\"fooBar\"}}</template>',\n      output: '<template>{{yield to=\"foo-bar\"}}</template>',\n      options: ['kebab-case'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"kebab-case\" naming format. Please change \"fooBar\" to \"foo-bar\".',\n        },\n      ],\n    },\n\n    {\n      code: '<template>{{if (has-block \"foo-bar\")}}</template>',\n      output: '<template>{{if (has-block \"fooBar\")}}</template>',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '<template>{{has-block-params \"foo-bar\"}}</template>',\n      output: '<template>{{has-block-params \"fooBar\"}}</template>',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '<template>{{if (has-block-params \"foo-bar\")}}</template>',\n      output: '<template>{{if (has-block-params \"fooBar\")}}</template>',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-require-valid-named-block-naming-format', rule, {\n  valid: [\n    // Default config (camelCase).\n    '{{yield}}',\n    '{{yield to=\"fooBar\"}}',\n    '{{has-block}}',\n    '{{has-block \"fooBar\"}}',\n    { code: '{{yield to=\"foo-bar\"}}', options: [false] },\n    '{{if (has-block)}}',\n    '{{if (has-block \"fooBar\")}}',\n    '{{has-block-params}}',\n    '{{has-block-params \"fooBar\"}}',\n    '{{if (has-block-params)}}',\n    '{{if (has-block-params \"fooBar\")}}',\n\n    // Explicit config: camelCase.\n    {\n      code: '{{yield to=\"fooBar\"}}',\n      options: ['camelCase'],\n    },\n    {\n      code: '{{has-block \"fooBar\"}}',\n      options: ['camelCase'],\n    },\n    {\n      code: '{{if (has-block \"fooBar\")}}',\n      options: ['camelCase'],\n    },\n    {\n      code: '{{has-block-params \"fooBar\"}}',\n      options: ['camelCase'],\n    },\n    {\n      code: '{{if (has-block-params \"fooBar\")}}',\n      options: ['camelCase'],\n    },\n\n    // Explicit config: kebab-case.\n    {\n      code: '{{yield to=\"foo-bar\"}}',\n      options: ['kebab-case'],\n    },\n    {\n      code: '{{has-block \"foo-bar\"}}',\n      options: ['kebab-case'],\n    },\n    {\n      code: '{{if (has-block \"foo-bar\")}}',\n      options: ['kebab-case'],\n    },\n    {\n      code: '{{has-block-params \"foo-bar\"}}',\n      options: ['kebab-case'],\n    },\n    {\n      code: '{{if (has-block-params \"foo-bar\")}}',\n      options: ['kebab-case'],\n    },\n  ],\n  invalid: [\n    // Default config (camelCase).\n    {\n      code: '{{yield to=\"foo-bar\"}}',\n      output: '{{yield to=\"fooBar\"}}',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{has-block \"foo-bar\"}}',\n      output: '{{has-block \"fooBar\"}}',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{if (has-block \"foo-bar\")}}',\n      output: '{{if (has-block \"fooBar\")}}',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{has-block-params \"foo-bar\"}}',\n      output: '{{has-block-params \"fooBar\"}}',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{if (has-block-params \"foo-bar\")}}',\n      output: '{{if (has-block-params \"fooBar\")}}',\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n\n    // Explicit config: camelCase.\n    {\n      code: '{{yield to=\"foo-bar\"}}',\n      output: '{{yield to=\"fooBar\"}}',\n      options: ['camelCase'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{has-block \"foo-bar\"}}',\n      output: '{{has-block \"fooBar\"}}',\n      options: ['camelCase'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{if (has-block \"foo-bar\")}}',\n      output: '{{if (has-block \"fooBar\")}}',\n      options: ['camelCase'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{has-block-params \"foo-bar\"}}',\n      output: '{{has-block-params \"fooBar\"}}',\n      options: ['camelCase'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n    {\n      code: '{{if (has-block-params \"foo-bar\")}}',\n      output: '{{if (has-block-params \"fooBar\")}}',\n      options: ['camelCase'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"camelCase\" naming format. Please change \"foo-bar\" to \"fooBar\".',\n        },\n      ],\n    },\n\n    // Explicit config: kebab-case.\n    {\n      code: '{{yield to=\"fooBar\"}}',\n      output: '{{yield to=\"foo-bar\"}}',\n      options: ['kebab-case'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"kebab-case\" naming format. Please change \"fooBar\" to \"foo-bar\".',\n        },\n      ],\n    },\n    {\n      code: '{{has-block \"fooBar\"}}',\n      output: '{{has-block \"foo-bar\"}}',\n      options: ['kebab-case'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"kebab-case\" naming format. Please change \"fooBar\" to \"foo-bar\".',\n        },\n      ],\n    },\n    {\n      code: '{{if (has-block \"fooBar\")}}',\n      output: '{{if (has-block \"foo-bar\")}}',\n      options: ['kebab-case'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"kebab-case\" naming format. Please change \"fooBar\" to \"foo-bar\".',\n        },\n      ],\n    },\n    {\n      code: '{{has-block-params \"fooBar\"}}',\n      output: '{{has-block-params \"foo-bar\"}}',\n      options: ['kebab-case'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"kebab-case\" naming format. Please change \"fooBar\" to \"foo-bar\".',\n        },\n      ],\n    },\n    {\n      code: '{{if (has-block-params \"fooBar\")}}',\n      output: '{{if (has-block-params \"foo-bar\")}}',\n      options: ['kebab-case'],\n      errors: [\n        {\n          message:\n            'Named blocks are required to use the \"kebab-case\" naming format. Please change \"fooBar\" to \"foo-bar\".',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-self-closing-void-elements.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-self-closing-void-elements');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-self-closing-void-elements', rule, {\n  valid: [\n    `<template>\n      <div></div>\n    </template>`,\n    {\n      code: `<template>\n        <img src=\"foo.jpg\" />\n      </template>`,\n      options: [false],\n    },\n    `<template>\n      <img src=\"foo.jpg\">\n    </template>`,\n    `<template>\n      <br>\n    </template>`,\n    `<template>\n      <input type=\"text\">\n    </template>`,\n\n    '<template><area></template>',\n    '<template><base></template>',\n    '<template><br></template>',\n    '<template><col></template>',\n    '<template><command></template>',\n    '<template><embed></template>',\n    '<template><hr></template>',\n    '<template><img></template>',\n    '<template><input></template>',\n    '<template><keygen></template>',\n    '<template><link></template>',\n    '<template><meta></template>',\n    '<template><param></template>',\n    '<template><source></template>',\n    '<template><track></template>',\n    '<template><wbr></template>',\n\n    // 'require' config — self-closing void elements are valid.\n    { code: '<template><area/></template>', options: ['require'] },\n    { code: '<template><base/></template>', options: ['require'] },\n    { code: '<template><br/></template>', options: ['require'] },\n    { code: '<template><col/></template>', options: ['require'] },\n    { code: '<template><command/></template>', options: ['require'] },\n    { code: '<template><embed/></template>', options: ['require'] },\n    { code: '<template><hr/></template>', options: ['require'] },\n    { code: '<template><img/></template>', options: ['require'] },\n    { code: '<template><input/></template>', options: ['require'] },\n    { code: '<template><keygen/></template>', options: ['require'] },\n    { code: '<template><link/></template>', options: ['require'] },\n    { code: '<template><meta/></template>', options: ['require'] },\n    { code: '<template><param/></template>', options: ['require'] },\n    { code: '<template><source/></template>', options: ['require'] },\n    { code: '<template><track/></template>', options: ['require'] },\n    { code: '<template><wbr/></template>', options: ['require'] },\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <img src=\"foo.jpg\" />\n      </template>`,\n      output: `<template>\n        <img src=\"foo.jpg\">\n      </template>`,\n      errors: [\n        {\n          message: 'Self-closing a void element is redundant',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <br />\n      </template>`,\n      output: `<template>\n        <br>\n      </template>`,\n      errors: [\n        {\n          message: 'Self-closing a void element is redundant',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <input type=\"text\" />\n      </template>`,\n      output: `<template>\n        <input type=\"text\">\n      </template>`,\n      errors: [\n        {\n          message: 'Self-closing a void element is redundant',\n          type: 'GlimmerElementNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><area/></template>',\n      output: '<template><area></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><base/></template>',\n      output: '<template><base></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><br/></template>',\n      output: '<template><br></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><col/></template>',\n      output: '<template><col></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><command/></template>',\n      output: '<template><command></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><embed/></template>',\n      output: '<template><embed></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><hr/></template>',\n      output: '<template><hr></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><img/></template>',\n      output: '<template><img></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><input/></template>',\n      output: '<template><input></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><keygen/></template>',\n      output: '<template><keygen></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><link/></template>',\n      output: '<template><link></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><meta/></template>',\n      output: '<template><meta></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><param/></template>',\n      output: '<template><param></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><source/></template>',\n      output: '<template><source></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><track/></template>',\n      output: '<template><track></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n    {\n      code: '<template><wbr/></template>',\n      output: '<template><wbr></template>',\n      errors: [{ message: 'Self-closing a void element is redundant' }],\n    },\n\n    // 'require' config — non-self-closing void elements are invalid.\n    {\n      code: '<template><area></template>',\n      output: '<template><area/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><base></template>',\n      output: '<template><base/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><br></template>',\n      output: '<template><br/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><col></template>',\n      output: '<template><col/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><command></template>',\n      output: '<template><command/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><embed></template>',\n      output: '<template><embed/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><hr></template>',\n      output: '<template><hr/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><img></template>',\n      output: '<template><img/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><input></template>',\n      output: '<template><input/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><keygen></template>',\n      output: '<template><keygen/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><link></template>',\n      output: '<template><link/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><meta></template>',\n      output: '<template><meta/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><param></template>',\n      output: '<template><param/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><source></template>',\n      output: '<template><source/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><track></template>',\n      output: '<template><track/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<template><wbr></template>',\n      output: '<template><wbr/></template>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-self-closing-void-elements', rule, {\n  valid: [\n    { code: '<br/>', options: [false] },\n    '<area>',\n    '<base>',\n    '<br>',\n    '<col>',\n    '<command>',\n    '<embed>',\n    '<hr>',\n    '<img>',\n    '<input>',\n    '<keygen>',\n    '<link>',\n    '<meta>',\n    '<param>',\n    '<source>',\n    '<track>',\n    '<wbr>',\n    // 'require' config — self-closing void elements are valid.\n    { code: '<area/>', options: ['require'] },\n    { code: '<base/>', options: ['require'] },\n    { code: '<br/>', options: ['require'] },\n    { code: '<col/>', options: ['require'] },\n    { code: '<command/>', options: ['require'] },\n    { code: '<embed/>', options: ['require'] },\n    { code: '<hr/>', options: ['require'] },\n    { code: '<img/>', options: ['require'] },\n    { code: '<input/>', options: ['require'] },\n    { code: '<keygen/>', options: ['require'] },\n    { code: '<link/>', options: ['require'] },\n    { code: '<meta/>', options: ['require'] },\n    { code: '<param/>', options: ['require'] },\n    { code: '<source/>', options: ['require'] },\n    { code: '<track/>', options: ['require'] },\n    { code: '<wbr/>', options: ['require'] },\n    // Complex void element with attributes, modifiers, comments, and block params\n    'foo<wbr data-custom=\"50\" {{my-modifier true \"baz\"}} {{!comment}} as |paramA paramB| >bar',\n    {\n      code: 'foo<wbr data-custom=\"50\" {{my-modifier true \"baz\"}} {{!comment}} as |paramA paramB| />bar',\n      options: ['require'],\n    },\n  ],\n  invalid: [\n    { code: '<area/>', output: '<area>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<base/>', output: '<base>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<br/>', output: '<br>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<col/>', output: '<col>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<command/>', output: '<command>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<embed/>', output: '<embed>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<hr/>', output: '<hr>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<img/>', output: '<img>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<input/>', output: '<input>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<keygen/>', output: '<keygen>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<link/>', output: '<link>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<meta/>', output: '<meta>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<param/>', output: '<param>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<source/>', output: '<source>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<track/>', output: '<track>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    { code: '<wbr/>', output: '<wbr>', errors: [{ messageId: 'redundantSelfClosing' }] },\n    // 'require' config — non-self-closing void elements are invalid.\n    {\n      code: '<area>',\n      output: '<area/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<base>',\n      output: '<base/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<br>',\n      output: '<br/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<col>',\n      output: '<col/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<command>',\n      output: '<command/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<embed>',\n      output: '<embed/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<hr>',\n      output: '<hr/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<img>',\n      output: '<img/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<input>',\n      output: '<input/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<keygen>',\n      output: '<keygen/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<link>',\n      output: '<link/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<meta>',\n      output: '<meta/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<param>',\n      output: '<param/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<source>',\n      output: '<source/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<track>',\n      output: '<track/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    {\n      code: '<wbr>',\n      output: '<wbr/>',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n    // Complex void element with attributes, modifiers, comments, and block params\n    {\n      code: 'foo<wbr data-custom=\"50\" {{my-modifier true \"baz\"}} {{!comment}} as |paramA paramB| />bar',\n      output:\n        'foo<wbr data-custom=\"50\" {{my-modifier true \"baz\"}} {{!comment}} as |paramA paramB| >bar',\n      errors: [{ messageId: 'redundantSelfClosing' }],\n    },\n    {\n      code: 'foo<wbr data-custom=\"50\" {{my-modifier true \"baz\"}} {{!comment}} as |paramA paramB| >bar',\n      output:\n        'foo<wbr data-custom=\"50\" {{my-modifier true \"baz\"}} {{!comment}} as |paramA paramB| />bar',\n      options: ['require'],\n      errors: [{ messageId: 'requireSelfClosing' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-simple-modifiers.js",
    "content": "const rule = require('../../../lib/rules/template-simple-modifiers');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-simple-modifiers', rule, {\n  valid: [\n    '<template><div {{(modifier \"track-interaction\" @controlName)}}></div></template>',\n    '<template><div {{(modifier trackInteraction @controlName)}}></div></template>',\n    '<template><div {{(modifier this.trackInteraction @controlName)}}></div></template>',\n    '<template><div {{my-modifier}}></div></template>',\n\n    '<template><div {{(modifier @trackInteraction @controlName)}}></div></template>',\n    '<template><div {{(if @isActionVisible (modifier \"track-interaction\" eventName=myEventName eventBody=myEventbody))}}></div></template>',\n    '<template><div {{(my-modifier (unless this.hasBeenClicked \"track-interaction\") \"click\" customizeData=this.customizeClickData)}}></div></template>',\n    '<template><MyComponent @people={{array \"Tom Dale\" \"Yehuda Katz\" this.myOtherPerson}} /></template>',\n    '<template><div {{(if this.foo (modifier \"foo-bar\"))}}></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div {{(modifier)}}></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The modifier helper should have a string or a variable name containing the modifier name as a first argument.',\n        },\n      ],\n    },\n\n    {\n      code: '<template><div {{(modifier (unless this.hasBeenClicked \"track-interaction\") \"click\" customizeData=this.customizeClickData)}}></div></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The modifier helper should have a string or a variable name containing the modifier name as a first argument.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-simple-modifiers', rule, {\n  valid: [\n    '<div {{(modifier \"track-interaction\" @controlName)}}></div>',\n    '<div {{(modifier trackInteraction @controlName)}}></div>',\n    '<div {{(modifier this.trackInteraction @controlName)}}></div>',\n    '<div {{(modifier @trackInteraction @controlName)}}></div>',\n    '<div {{(if @isActionVisible (modifier \"track-interaction\" eventName=myEventName eventBody=myEventbody))}}></div>',\n    '<div {{(my-modifier (unless this.hasBeenClicked \"track-interaction\") \"click\" customizeData=this.customizeClickData)}}></div>',\n    '<div {{my-modifier}}></div>',\n    '<MyComponent @people={{array \"Tom Dale\" \"Yehuda Katz\" this.myOtherPerson}} />',\n    '<div {{(if this.foo (modifier \"foo-bar\"))}}></div>',\n  ],\n  invalid: [\n    {\n      code: '<div {{(modifier (unless this.hasBeenClicked \"track-interaction\") \"click\" customizeData=this.customizeClickData)}}></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The modifier helper should have a string or a variable name containing the modifier name as a first argument.',\n        },\n      ],\n    },\n    {\n      code: '<div {{(modifier)}}></div>',\n      output: null,\n      errors: [\n        {\n          message:\n            'The modifier helper should have a string or a variable name containing the modifier name as a first argument.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-simple-unless.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-simple-unless');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-simple-unless', rule, {\n  valid: [\n    '<template>{{#unless isHidden}}Show{{/unless}}</template>',\n    '<template>{{#unless @disabled}}Enabled{{/unless}}</template>',\n\n    \"<template>{{#unless isRed}}I'm blue, da ba dee da ba daa{{/unless}}</template>\",\n    '<template><div class=\"{{unless foo \\'no-foo\\'}}\"></div></template>',\n    '<template><div class=\"{{if foo \\'foo\\'}}\"></div></template>',\n    '<template>{{unrelated-mustache-without-params}}</template>',\n    '<template>{{#if foo}}{{else}}{{/if}}</template>',\n    '<template>{{#if foo}}{{else}}{{#unless bar}}{{/unless}}{{/if}}</template>',\n    '<template>{{#if foo}}{{else}}{{unless bar someProperty}}{{/if}}</template>',\n    '<template>{{#unless (or foo bar)}}order whiskey{{/unless}}</template>',\n    {\n      code: '<template>{{#unless (eq (or foo bar) baz)}}order whiskey{{/unless}}</template>',\n      options: [{ maxHelpers: 2 }],\n    },\n    '<template>{{#unless hamburger}}\\\\n  HOT DOG!\\\\n{{/unless}}</template>',\n  ],\n  invalid: [\n    {\n      code: '<template>{{#unless (eq value 1)}}Not one{{/unless}}</template>',\n      output: '<template>{{#if (not (eq value 1))}}Not one{{/if}}</template>',\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: '<template>{{#unless (or a b)}}Neither{{/unless}}</template>',\n      output: '<template>{{#if (not (or a b))}}Neither{{/if}}</template>',\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n\n    {\n      code: \"<template>{{unless (if true)  'Please no'}}</template>\",\n      output: \"<template>{{if (not (if true))  'Please no'}}</template>\",\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: \"<template>{{unless (and isBad isAwful)  'notBadAndAwful'}}</template>\",\n      output: \"<template>{{if (not (and isBad isAwful))  'notBadAndAwful'}}</template>\",\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: '<template><img class={{unless (not condition) \"some-class\"}}></template>',\n      output: '<template><img class={{if condition \"some-class\"}}></template>',\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: '<template><img class={{unless (one condition) \"some-class\" \"other-class\"}}></template>',\n      output:\n        '<template><img class={{if (not (one condition)) \"some-class\" \"other-class\"}}></template>',\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // followingElseBlock: simple body/inverse swap with `unless` -> `if`\n    {\n      code: '<template>{{#unless show}}A{{else}}B{{/unless}}</template>',\n      output: '<template>{{#if show}}B{{else}}A{{/if}}</template>',\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-simple-unless', rule, {\n  valid: [\n    \"{{#unless isRed}}I'm blue, da ba dee da ba daa{{/unless}}\",\n    '<div class=\"{{unless foo \\'no-foo\\'}}\"></div>',\n    '<div class=\"{{if foo \\'foo\\'}}\"></div>',\n    '{{unrelated-mustache-without-params}}',\n    '{{#if foo}}{{else}}{{/if}}',\n    '{{#if foo}}{{else}}{{#unless bar}}{{/unless}}{{/if}}',\n    '{{#if foo}}{{else}}{{unless bar someProperty}}{{/if}}',\n    '{{#unless (or foo bar)}}order whiskey{{/unless}}',\n    {\n      code: '{{#unless (eq (or foo bar) baz)}}order whiskey{{/unless}}',\n      options: [{ maxHelpers: 2 }],\n    },\n    '{{unless foo bar}}',\n    '{{unless (eq foo bar) baz}}',\n    '{{unless (and isBad isAwful) \"notBadAndAwful\"}}',\n    '<img class={{unless (not condition) \"some-class\"}}>',\n    '<img class={{unless (one condition) \"some-class\" \"other-class\"}}>',\n    ['{{#unless hamburger}}', '  HOT DOG!', '{{/unless}}'].join('\\n'),\n    // allowlist + maxHelpers\n    {\n      code: '{{unless (eq foo bar) baz}}',\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n    },\n    {\n      code: '{{#unless (eq foo bar)}}baz{{/unless}}',\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n    },\n    {\n      code: '{{unless (eq (not foo) bar) baz}}',\n      options: [{ allowlist: [], maxHelpers: 2 }],\n    },\n    {\n      code: '{{#unless (eq (not foo) bar)}}baz{{/unless}}',\n      options: [{ allowlist: [], maxHelpers: 2 }],\n    },\n    {\n      code: '{{#unless (eq (not foo) bar)}}baz{{/unless}}',\n      options: [{ maxHelpers: 2 }],\n    },\n    {\n      code: '{{#unless (eq (not foo) bar)}}baz{{/unless}}',\n      options: [{ maxHelpers: -1 }],\n    },\n    {\n      code: '{{#unless (eq (not foo) bar)}}baz{{/unless}}',\n      options: [{ maxHelpers: -1, denylist: [] }],\n    },\n    {\n      code: '{{#unless (eq (not foo) bar)}}baz{{/unless}}',\n      options: [{ maxHelpers: -1, denylist: ['or'] }],\n    },\n    // valid with ETL-style allowlist config: helpers in allowlist, count within maxHelpers\n    {\n      code: '{{#unless (or foo bar)}}order whiskey{{/unless}}',\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n    },\n    {\n      code: '{{#unless (eq (or foo bar) baz)}}order whiskey{{/unless}}',\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n    },\n  ],\n  invalid: [\n    // inline unless with nested helpers (exceeds default maxHelpers=1)\n    {\n      code: \"{{unless (if (or true))  'Please no'}}\",\n      output: \"{{if (not (if (or true)))  'Please no'}}\",\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // inline unless with helper — maxHelpers: 0\n    {\n      code: \"{{unless (if true)  'Please no'}}\",\n      output: \"{{if (not (if true))  'Please no'}}\",\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: \"{{unless (and isBad isAwful)  'notBadAndAwful'}}\",\n      output: \"{{if (not (and isBad isAwful))  'notBadAndAwful'}}\",\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: '<img class={{unless (not condition) \"some-class\"}}>',\n      output: '<img class={{if condition \"some-class\"}}>',\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: '<img class={{unless (one condition) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{if (not (one condition)) \"some-class\" \"other-class\"}}>',\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // {{else}} block with {{unless}} — fixable\n    {\n      code: [\n        '{{#unless bandwagoner}}',\n        '  Go Niners!',\n        '{{else}}',\n        '  Go Seahawks!',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: ['{{#if bandwagoner}}', '  Go Seahawks!', '{{else}}', '  Go Niners!', '{{/if}}'].join(\n        '\\n'\n      ),\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    {\n      code: ['{{#unless bandwagoner}}', 'Test1', '{{else}}', 'Test2', '{{/unless}}'].join('\\n'),\n      output: ['{{#if bandwagoner}}', 'Test2', '{{else}}', 'Test1', '{{/if}}'].join('\\n'),\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    {\n      code: [\n        '{{#unless bandwagoner}}',\n        '{{else}}',\n        '  {{#my-component}}',\n        '  {{/my-component}}',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if bandwagoner}}',\n        '  {{#my-component}}',\n        '  {{/my-component}}',\n        '{{else}}',\n        '{{/if}}',\n      ].join('\\n'),\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    // {{else if}} with {{unless}}\n    {\n      code: [\n        '{{#unless bandwagoner}}',\n        '  Go Niners!',\n        '{{else if goHawks}}',\n        '  Go Seahawks!',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: null,\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    {\n      code: [\n        '{{#unless bandwagoner}}',\n        '  Go Niners!',\n        '{{else if goPats}}',\n        '  Tom Brady is GOAT',\n        '{{else if goHawks}}',\n        '  Go Seahawks!',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: null,\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    {\n      code: [\n        '{{#unless bandwagoner}}',\n        '  Go Niners!',\n        '{{else if goBengals}}',\n        '  Ouch, sorry',\n        '{{else}}',\n        '  Go Seahawks!',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: null,\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    // {{else unless}}\n    {\n      code: ['{{#if dog}}', '  Ruff Ruff!', '{{else unless cat}}', '  not cat', '{{/if}}'].join(\n        '\\n'\n      ),\n      output: null,\n      errors: [{ messageId: 'asElseUnlessBlock' }],\n    },\n    // block unless with disallowed helper (via allowlist)\n    {\n      code: ['{{#unless (and isFruit isYellow)}}', '  I am a green celery!', '{{/unless}}'].join(\n        '\\n'\n      ),\n      output: ['{{#if (not (and isFruit isYellow))}}', '  I am a green celery!', '{{/if}}'].join(\n        '\\n'\n      ),\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: [\n        '{{#unless (not isBrown isSticky)}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: ['{{#if (or isBrown isSticky)}}', '  I think I am a brown stick', '{{/if}}'].join(\n        '\\n'\n      ),\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: ['{{#unless (not isBrown)}}', '  I think I am a brown', '{{/unless}}'].join('\\n'),\n      output: ['{{#if isBrown}}', '  I think I am a brown', '{{/if}}'].join('\\n'),\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: [\n        '{{#unless isSticky}}',\n        '  I think I am a brown stick',\n        '{{else}}',\n        '  Not a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if isSticky}}',\n        '  Not a brown stick',\n        '{{else}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      errors: [{ messageId: 'followingElseBlock' }],\n    },\n    // maxHelpers exceeded (top-level params only)\n    {\n      code: [\n        '{{#unless (or foo bar) (eq baz beer) (not-eq x y)}}',\n        '  MUCH HELPERS, VERY BAD',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (or foo bar)) (eq baz beer) (not-eq x y)}}',\n        '  MUCH HELPERS, VERY BAD',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // maxHelpers: 0\n    {\n      code: [\n        '{{#unless (concat \"blue\" \"red\")}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (concat \"blue\" \"red\"))}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ maxHelpers: 0 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // allowlist — helper not in allowlist\n    {\n      code: ['{{#unless (one foo bar)}}', '  I think I am a brown stick', '{{/unless}}'].join('\\n'),\n      output: ['{{#if (not (one foo bar))}}', '  I think I am a brown stick', '{{/if}}'].join('\\n'),\n      options: [{ allowlist: ['test'], maxHelpers: 1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // maxHelpers exceeded with multiple top-level subexpressions\n    {\n      code: [\n        '{{#unless (one foo) (two bar) (three baz)}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (one foo)) (two bar) (three baz)}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // denylist — helper in denylist\n    {\n      code: ['{{#unless (two three)}}', '  I think I am a brown stick', '{{/unless}}'].join('\\n'),\n      output: ['{{#if (not (two three))}}', '  I think I am a brown stick', '{{/if}}'].join('\\n'),\n      options: [{ denylist: ['two'], maxHelpers: -1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    {\n      code: ['{{#unless (two three)}}', '  I think I am a brown stick', '{{/unless}}'].join('\\n'),\n      output: ['{{#if (not (two three))}}', '  I think I am a brown stick', '{{/if}}'].join('\\n'),\n      options: [{ denylist: ['two', 'four'], maxHelpers: -1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // --- ETL-equivalent invalid cases with allowlist config ---\n    // allowlist violation: `if` not in allowlist (ETL bad case: {{unless (if (or true)) ...}})\n    {\n      code: \"{{unless (if (or true))  'Please no'}}\",\n      output: \"{{if (not (if (or true)))  'Please no'}}\",\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // allowlist violation: `if` not in allowlist (ETL bad case: {{unless (if true) ...}})\n    {\n      code: \"{{unless (if true)  'Please no'}}\",\n      output: \"{{if (not (if true))  'Please no'}}\",\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // allowlist violation: `and` not in allowlist (ETL bad case: {{unless (and isBad isAwful) ...}})\n    {\n      code: \"{{unless (and isBad isAwful)  'notBadAndAwful'}}\",\n      output: \"{{if (not (and isBad isAwful))  'notBadAndAwful'}}\",\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // allowlist violation: `not` not in allowlist (ETL bad case: inline unless with not)\n    {\n      code: '<img class={{unless (not condition) \"some-class\"}}>',\n      output: '<img class={{if condition \"some-class\"}}>',\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // allowlist violation: `one` not in allowlist (ETL bad case: inline unless with one)\n    {\n      code: '<img class={{unless (one condition) \"some-class\" \"other-class\"}}>',\n      output: '<img class={{if (not (one condition)) \"some-class\" \"other-class\"}}>',\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // maxHelpers exceeded with nested allowed helpers (ETL bad case: 3 helpers > maxHelpers 2)\n    {\n      code: [\n        '{{#unless (or (eq foo bar) (not-eq baz \"beer\"))}}',\n        '  MUCH HELPERS, VERY BAD',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (or (eq foo bar) (not-eq baz \"beer\")))}}',\n        '  MUCH HELPERS, VERY BAD',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ allowlist: ['or', 'eq', 'not-eq'], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // allowlist violation with nested helpers (ETL bad case: allowlist=['test'], one not in allowlist)\n    {\n      code: [\n        '{{#unless (one (test power) two)}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (one (test power) two))}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ allowlist: ['test'], maxHelpers: 1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // maxHelpers exceeded with empty allowlist and nested helpers (ETL bad case: 3 helpers > maxHelpers 2)\n    {\n      code: [\n        '{{#unless (one (two three) (four five))}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (one (two three) (four five)))}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ allowlist: [], maxHelpers: 2 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // denylist with nested helpers (ETL bad case: denylist=['two'], two is in nested position)\n    {\n      code: [\n        '{{#unless (one (two three) (four five))}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (one (two three) (four five)))}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ denylist: ['two'], maxHelpers: -1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // multi-item denylist with nested helpers (ETL bad case: denylist=['two','four'])\n    {\n      code: [\n        '{{#unless (one (two three) (four five))}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (one (two three) (four five)))}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ denylist: ['two', 'four'], maxHelpers: -1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n    // denylist violation: `one` in denylist (ETL-style with denylist=['one'])\n    {\n      code: [\n        '{{#unless (one (two three) (four five))}}',\n        '  I think I am a brown stick',\n        '{{/unless}}',\n      ].join('\\n'),\n      output: [\n        '{{#if (not (one (two three) (four five)))}}',\n        '  I think I am a brown stick',\n        '{{/if}}',\n      ].join('\\n'),\n      options: [{ denylist: ['one'], maxHelpers: -1 }],\n      errors: [{ messageId: 'withHelper' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-sort-invocations.js",
    "content": "const rule = require('../../../lib/rules/template-sort-invocations');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-sort-invocations', rule, {\n  valid: [\n    // Basic component\n    '<template><Button /></template>',\n\n    // Single attribute\n    '<template><Button @label=\"submit\" /></template>',\n\n    // Correctly sorted: @args first, then attributes, then modifiers\n    '<template><Button @isDisabled={{true}} @label=\"Submit\" class=\"button\" {{on \"click\" this.handleClick}} /></template>',\n\n    // Correctly sorted attributes\n    '<template><div class=\"foo\" id=\"bar\" /></template>',\n\n    // Correctly sorted modifiers\n    '<template><button {{on \"click\" this.handleClick}} {{on \"focus\" this.handleFocus}} /></template>',\n\n    // ...attributes at the end (after modifiers per rule)\n    '<template><Button @label=\"Submit\" class=\"button\" {{on \"click\" this.handleClick}} ...attributes /></template>',\n\n    // Hash pairs sorted\n    '<template>{{component name=\"button\" type=\"submit\"}}</template>',\n\n    // Block with sorted hash\n    '<template>{{#each items as |item|}}{{item}}{{/each}}</template>',\n    '<template>{{#let a=\"1\" b=\"2\" as |x y|}}{{x}}{{y}}{{/let}}</template>',\n  ],\n\n  invalid: [\n    // Unsorted attributes (regular before @arg)\n    {\n      code: '<template><Button class=\"button\" @label=\"Submit\" /></template>',\n      output: '<template><Button @label=\"Submit\" class=\"button\" /></template>',\n      errors: [\n        {\n          messageId: 'attributeOrder',\n          data: { attributeName: 'class', expectedAfter: '@label' },\n        },\n      ],\n    },\n\n    // Unsorted regular attributes\n    {\n      code: '<template><div id=\"bar\" class=\"foo\" /></template>',\n      output: '<template><div class=\"foo\" id=\"bar\" /></template>',\n      errors: [\n        {\n          messageId: 'attributeOrder',\n          data: { attributeName: 'id', expectedAfter: 'class' },\n        },\n      ],\n    },\n\n    // Unsorted modifiers\n    {\n      code: '<template><button {{on \"focus\" this.handleFocus}} {{on \"click\" this.handleClick}} /></template>',\n      output:\n        '<template><button {{on \"click\" this.handleClick}} {{on \"focus\" this.handleFocus}} /></template>',\n      errors: [\n        {\n          messageId: 'modifierOrder',\n          data: { modifierName: 'on', expectedAfter: 'on' },\n        },\n      ],\n    },\n\n    // ...attributes before modifiers\n    {\n      code: '<template><Button @label=\"Submit\" ...attributes {{on \"click\" this.handleClick}} /></template>',\n      output:\n        '<template><Button @label=\"Submit\" {{on \"click\" this.handleClick}} ...attributes /></template>',\n      errors: [\n        {\n          messageId: 'splattributesOrder',\n        },\n      ],\n    },\n\n    // Unsorted hash pairs\n    {\n      code: '<template>{{component type=\"submit\" name=\"button\"}}</template>',\n      output: '<template>{{component name=\"button\" type=\"submit\"}}</template>',\n      errors: [\n        {\n          messageId: 'hashPairOrder',\n          data: { hashPairName: 'type', expectedAfter: 'name' },\n        },\n      ],\n    },\n\n    // Unsorted hash in block\n    {\n      code: '<template>{{#let b=\"2\" a=\"1\" as |x y|}}{{x}}{{y}}{{/let}}</template>',\n      output: '<template>{{#let a=\"1\" b=\"2\" as |x y|}}{{x}}{{y}}{{/let}}</template>',\n      errors: [\n        {\n          messageId: 'hashPairOrder',\n          data: { hashPairName: 'b', expectedAfter: 'a' },\n        },\n      ],\n    },\n\n    {\n      code: `<template>\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button=\"\"\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          onclick=this.doSomething\n          type=\"submit\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          onclick=this.doSomething\n          type=\"submit\"\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      </template>`,\n      output: `<template>\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button\n          ...attributes\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button=\"\"\n          ...attributes\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          onclick=this.doSomething\n          data-test-button=\"\"\n          type=\"submit\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          onclick=this.doSomething\n          data-test-button=\"\"\n          type=\"submit\"\n        }}\n          Submit form\n        {{/ui/button}}\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button=\"\"\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          type=\"submit\"\n          isDisabled=true\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          type=\"submit\"\n          isDisabled=true\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      </template>`,\n      output: `<template>\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" this.doSomething}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" this.doSomething}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button=\"\"\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          isDisabled=true\n          type=\"submit\"\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          isDisabled=true\n          type=\"submit\"\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" @onSubmit}}\n          @type=\"submit\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" @onSubmit}}\n          @type=\"submit\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button=\"\"\n          {{autofocus}}\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          type=\"submit\"\n          isDisabled=(not this.enableSubmit)\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          type=\"submit\"\n          isDisabled=(not this.enableSubmit)\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      </template>`,\n      output: `<template>\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" @onSubmit}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" @onSubmit}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button=\"\"\n          {{autofocus}}\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          isDisabled=(not this.enableSubmit)\n          type=\"submit\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          isDisabled=(not this.enableSubmit)\n          type=\"submit\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'modifierOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'modifierOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <MyComponent\n          @title=\"Update history\"\n          @description={{if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          }}\n        />\n\n        {{my-component\n          title=\"Update history\"\n          description=(if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          )\n        }}\n      </template>`,\n      output: `<template>\n        <MyComponent\n          @description={{if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          }}\n          @title=\"Update history\"\n        />\n\n        {{my-component\n          description=(if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          )\n          title=\"Update history\"\n        }}\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'hashPairOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{! @glint-expect-error: @onSubmit has incorrect type }}\n          {{on \"click\" @onSubmit}}\n          @type=\"submit\"\n          {{!-- @glint-expect-error: this.enableSubmit has incorrect type --}}\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n      </template>`,\n      output: `<template>\n        <Ui::Button\n          @type=\"submit\"\n          {{! @glint-expect-error: @onSubmit has incorrect type }}\n          {{on \"click\" @onSubmit}}\n          data-cucumber-button=\"Submit form\"\n          {{!-- @glint-expect-error: this.enableSubmit has incorrect type --}}\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n      </template>`,\n      errors: [{ messageId: 'attributeOrder' }, { messageId: 'modifierOrder' }],\n    },\n    {\n      code: `<template>\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button\n        >\n          Submit form\n        </this.MyButton>\n      </template>`,\n      output: `<template>\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button\n          ...attributes\n          @label=\"Submit form\"\n        />\n\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button\n          ...attributes\n        >\n          Submit form\n        </this.MyButton>\n      </template>`,\n      errors: [{ messageId: 'attributeOrder' }, { messageId: 'attributeOrder' }],\n    },\n    {\n      code: `<template>\n        {{component \"ui/button\"}}\n\n        {{component \"ui/button\"\n          onClick=this.doSomething\n          type=\"submit\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=this.doSomething\n          type=\"submit\"\n          isDisabled=true\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=@onSubmit\n          type=\"submit\"\n          isDisabled=(not this.enableSubmit)\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n      </template>`,\n      output: `<template>\n        {{component \"ui/button\"}}\n\n        {{component \"ui/button\"\n          onClick=this.doSomething\n          data-test-button=\"\"\n          type=\"submit\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=this.doSomething\n          isDisabled=true\n          type=\"submit\"\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=@onSubmit\n          isDisabled=(not this.enableSubmit)\n          type=\"submit\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" @onSubmit}}\n          type=\"submit\"\n          disabled\n          class={{local this.styles \"button\" \"disabled\"}}\n          ...attributes\n          data-test-button\n          {{autofocus}}\n        >\n          Submit form\n        </button>\n\n        <div\n          role=\"button\"\n          {{on \"mouseleave\" (fn this.setFocus false)}}\n          class={{local\n            this.styles\n            \"button\"\n            (if this.isFocused \"focused\")\n          }}\n          {{on \"click\" this.trackEvent}}\n          {{on \"mouseenter\" (fn this.setFocus true)}}\n          {{on \"click\" this.submitForm}}\n        >\n          Submit form\n        </div>\n      </template>`,\n      output: `<template>\n        <button\n          data-cucumber-button=\"Submit form\"\n          {{autofocus}}\n          type=\"submit\"\n          disabled\n          class={{local this.styles \"button\" \"disabled\"}}\n          ...attributes\n          data-test-button\n          {{on \"click\" @onSubmit}}\n        >\n          Submit form\n        </button>\n\n        <div\n          class={{local\n            this.styles\n            \"button\"\n            (if this.isFocused \"focused\")\n          }}\n          {{on \"mouseleave\" (fn this.setFocus false)}}\n          role=\"button\"\n          {{on \"click\" this.trackEvent}}\n          {{on \"mouseenter\" (fn this.setFocus true)}}\n          {{on \"click\" this.submitForm}}\n        >\n          Submit form\n        </div>\n      </template>`,\n      errors: [\n        { messageId: 'modifierOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'modifierOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        {{#let (unique-id) as |formId|}}\n          <form\n            class={{this.styles.form}}\n            data-test-form={{if @title @title \"\"}}\n            aria-labelledby={{if @title (concat formId \"-title\")}}\n            aria-describedby={{if\n              @instructions\n              (concat formId \"-instructions\")\n            }}\n            {{autofocus}}\n            {{on \"submit\" this.submitForm}}\n          >\n            <Ui::Form::Information\n              @formId={{formId}}\n              @title={{@title}}\n              @instructions={{@instructions}}\n            />\n\n            <ContainerQuery\n              @features={{hash wide=(width min=480)}}\n              as |CQ|\n            >\n              {{yield\n                (hash\n                  Input=(component\n                    \"ui/form/input\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Textarea=(component\n                    \"ui/form/textarea\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Number=(component\n                    \"ui/form/number\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Checkbox=(component\n                    \"ui/form/checkbox\"\n                    changeset=this.changeset\n                    isInline=true\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Select=(component\n                    \"ui/form/select\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                )\n              }}\n            </ContainerQuery>\n\n            <div class={{this.styles.actions}}>\n              <button\n                type=\"submit\"\n                data-test-button=\"Submit\"\n                class={{this.styles.submit-button}}\n              >\n                {{t \"components.ui.form.submit\"}}\n              </button>\n            </div>\n          </form>\n        {{/let}}\n      </template>`,\n      output: `<template>\n        {{#let (unique-id) as |formId|}}\n          <form\n            class={{this.styles.form}}\n            aria-labelledby={{if @title (concat formId \"-title\")}}\n            data-test-form={{if @title @title \"\"}}\n            aria-describedby={{if\n              @instructions\n              (concat formId \"-instructions\")\n            }}\n            {{autofocus}}\n            {{on \"submit\" this.submitForm}}\n          >\n            <Ui::Form::Information\n              @formId={{formId}}\n              @instructions={{@instructions}}\n              @title={{@title}}\n            />\n\n            <ContainerQuery\n              @features={{hash wide=(width min=480)}}\n              as |CQ|\n            >\n              {{yield\n                (hash\n                  Input=(component\n                    \"ui/form/input\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Number=(component\n                    \"ui/form/number\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Textarea=(component\n                    \"ui/form/textarea\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Checkbox=(component\n                    \"ui/form/checkbox\"\n                    changeset=this.changeset\n                    isInline=true\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Select=(component\n                    \"ui/form/select\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                )\n              }}\n            </ContainerQuery>\n\n            <div class={{this.styles.actions}}>\n              <button\n                data-test-button=\"Submit\"\n                type=\"submit\"\n                class={{this.styles.submit-button}}\n              >\n                {{t \"components.ui.form.submit\"}}\n              </button>\n            </div>\n          </form>\n        {{/let}}\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'hashPairOrder' },\n        { messageId: 'attributeOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          ...attributes\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          ...attributes\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n      </template>`,\n      output: `<template>\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          ...attributes\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          {{did-insert this.doSomething1}}\n          ...attributes\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n      </template>`,\n      errors: [{ messageId: 'attributeOrder' }, { messageId: 'splattributesOrder' }],\n    },\n    {\n      code: `<template>\n        <Ui::Page\n          @title={{\"Your product\"}}\n          {{! @glint-expect-error: Type 'string | null' is not assignable to type 'string'. }}\n          @routeName={{this.router.currentRouteName}}\n          as |Page|\n        >\n          {{outlet}}\n\n          {{#if this.someCondition1}}\n            <Page.Button @id=\"products.overview\" @icon=\"rightarrow\" @label=\"\" />\n          {{else if this.someCondition2}}\n            <Page.Button @id=\"products.product\" @icon=\"\" @label=\"\" />\n          {{else}}\n            <Page.Button\n              @id=\"products.product\"\n              @icon=\"\"\n              @label=\"\n              \"\n            />\n          {{/if}}\n        </Ui::Page>\n      </template>`,\n      output: `<template>\n        <Ui::Page\n          @routeName={{this.router.currentRouteName}}\n          {{! @glint-expect-error: Type 'string | null' is not assignable to type 'string'. }}\n          @title={{\"Your product\"}}\n          as |Page|\n        >\n          {{outlet}}\n\n          {{#if this.someCondition1}}\n            <Page.Button @icon=\"rightarrow\" @id=\"products.overview\" @label=\"\" />\n          {{else if this.someCondition2}}\n            <Page.Button @icon=\"\" @id=\"products.product\" @label=\"\" />\n          {{else}}\n            <Page.Button\n              @icon=\"\"\n              @id=\"products.product\"\n              @label=\"\n              \"\n            />\n          {{/if}}\n        </Ui::Page>\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n      ],\n    },\n    {\n      code: `<template>\n        <MyComponent\n          @parentContainerId={{concat \"#\" @parentId}}\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          @style={{concat \".\" @type \"1\"}}\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          @className={{concat \"a\" @typeA \"b\" @typeB \"c\" @typeC \"d\"}}\n          aria-describedby=\"1\"\n        />\n\n        <input\n          type=\"tel\"\n          local-class=\"input {{concat 'flag-' @country}}\"\n        />\n\n        <MyComponent\n          @parentContainerId=\"#{{@parentId}}\"\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          @style=\".{{@type}}1\"\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          aria-describedby=\"1\"\n          @className=\"a{{@typeA}}b{{@typeB}}c{{@typeC}}d\"\n        />\n\n        <input\n          type=\"tel\"\n          local-class=\"input flag-{{@country}}\"\n        />\n      </template>`,\n      output: `<template>\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @parentContainerId={{concat \"#\" @parentId}}\n        />\n\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @style={{concat \".\" @type \"1\"}}\n        />\n\n        <MyComponent\n          @className={{concat \"a\" @typeA \"b\" @typeB \"c\" @typeC \"d\"}}\n          aria-describedby=\"1\"\n        />\n\n        <input\n          local-class=\"input {{concat 'flag-' @country}}\"\n          type=\"tel\"\n        />\n\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @parentContainerId=\"#{{@parentId}}\"\n        />\n\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @style=\".{{@type}}1\"\n        />\n\n        <MyComponent\n          @className=\"a{{@typeA}}b{{@typeB}}c{{@typeC}}d\"\n          aria-describedby=\"1\"\n        />\n\n        <input\n          local-class=\"input flag-{{@country}}\"\n          type=\"tel\"\n        />\n      </template>`,\n      errors: [\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n        { messageId: 'attributeOrder' },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-sort-invocations', rule, {\n  valid: [\n    '',\n    `\n        <Ui::Button />\n\n        <Ui::Button>\n          Submit form\n        </Ui::Button>\n\n        {{ui/button}}\n\n        {{#ui/button}}\n          Submit form\n        {{/ui/button}}\n      `,\n    `\n        <Ui::Button\n          @label=\"Submit form\"\n          @type=\"submit\"\n          data-test-button\n          {{on \"click\" this.doSomething}}\n          ...attributes\n        />\n      `,\n    `\n        <Ui::Button\n          @isDisabled={{true}}\n          @label=\"Submit form\"\n          @type=\"submit\"\n          class=\"ui-button disabled\"\n          data-cucumber-button=\"Submit form\"\n          data-test-button\n          {{on \"click\" this.doSomething}}\n          ...attributes\n        />\n      `,\n    `\n        <Ui::Button\n          @isDisabled={{not this.enableSubmit}}\n          @label=\"Submit form\"\n          @type=\"submit\"\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          data-cucumber-button=\"Submit form\"\n          data-test-button\n          {{autofocus}}\n          {{on \"click\" @onSubmit}}\n          ...attributes\n        />\n      `,\n    `\n        <MyComponent\n          @description={{if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              installedOn=this.installationDate\n              packageName=\"ember-source\"\n              packageVersion=\"6.0.0\"\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageName=\"ember-source\"\n                packageVersion=\"6.0.0\"\n              )\n            )\n          }}\n          @title=\"Update history\"\n        />\n      `,\n    `\n        <Ui::Button\n          {{!-- @glint-expect-error: this.enableSubmit has incorrect type --}}\n          @isDisabled={{not this.enableSubmit}}\n          @label=\"Submit form\"\n          @type=\"submit\"\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          data-cucumber-button=\"Submit form\"\n          data-test-button\n          {{autofocus}}\n          {{! @glint-expect-error: @onSubmit has incorrect type }}\n          {{on \"click\" @onSubmit}}\n          ...attributes\n        />\n      `,\n    `\n        <this.MyButton\n          @label=\"Submit form\"\n          @type=\"submit\"\n          data-test-button\n          {{on \"click\" this.doSomething}}\n          ...attributes\n        />\n      `,\n    `\n        {{component\n          \"ui/button\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-cucumber-button=\"Submit form\"\n          data-test-button=\"\"\n          isDisabled=(not this.enableSubmit)\n          label=\"Submit form\"\n          onClick=@onSubmit\n          type=\"submit\"\n        }}\n      `,\n    `\n        <button\n          class={{local this.styles \"button\" \"disabled\"}}\n          data-cucumber-button=\"Submit form\"\n          data-test-button\n          disabled\n          type=\"submit\"\n          {{autofocus}}\n          {{on \"click\" @onSubmit}}\n          ...attributes\n        >\n          Submit form\n        </button>\n\n        <div\n          class={{local\n            this.styles\n            \"button\"\n            (if this.isFocused \"focused\")\n          }}\n          role=\"button\"\n          {{on \"click\" this.submitForm}}\n          {{on \"click\" this.trackEvent}}\n          {{on \"mouseenter\" (fn this.setFocus true)}}\n          {{on \"mouseleave\" (fn this.setFocus false)}}\n        >\n          Submit form\n        </div>\n      `,\n    `\n        <MyComponent\n        >\n          <div\n          >\n            <span class={{this.styles.highlight}}>\n            Hello world!\n            </span>\n          </div>\n        </MyComponent>\n      `,\n  ],\n  invalid: [\n    {\n      code: `\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button=\"\"\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          onclick=this.doSomething\n          type=\"submit\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          onclick=this.doSomething\n          type=\"submit\"\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      `,\n      output: `\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button\n          ...attributes\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button=\"\"\n          ...attributes\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          onclick=this.doSomething\n          data-test-button=\"\"\n          type=\"submit\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          onclick=this.doSomething\n          data-test-button=\"\"\n          type=\"submit\"\n        }}\n          Submit form\n        {{/ui/button}}\n      `,\n      errors: [\n        { message: '`...attributes` must appear after `data-test-button`' },\n        { message: '`...attributes` must appear after `data-test-button`' },\n        { message: '`type` must appear after `data-test-button`' },\n        { message: '`type` must appear after `data-test-button`' },\n      ],\n    },\n    {\n      code: `\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button=\"\"\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          type=\"submit\"\n          isDisabled=true\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          type=\"submit\"\n          isDisabled=true\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      `,\n      output: `\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" this.doSomething}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" this.doSomething}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{true}}\n          class=\"ui-button disabled\"\n          ...attributes\n          data-test-button=\"\"\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          isDisabled=true\n          type=\"submit\"\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=this.doSomething\n          isDisabled=true\n          type=\"submit\"\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      `,\n      errors: [\n        { message: '`data-cucumber-button` must appear after `@type`' },\n        { message: '`data-cucumber-button` must appear after `@type`' },\n        { message: '`type` must appear after `isDisabled`' },\n        { message: '`type` must appear after `isDisabled`' },\n      ],\n    },\n    {\n      code: `\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" @onSubmit}}\n          @type=\"submit\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" @onSubmit}}\n          @type=\"submit\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button=\"\"\n          {{autofocus}}\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          type=\"submit\"\n          isDisabled=(not this.enableSubmit)\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          type=\"submit\"\n          isDisabled=(not this.enableSubmit)\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      `,\n      output: `\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" @onSubmit}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n\n        <Ui::Button\n          @type=\"submit\"\n          {{on \"click\" @onSubmit}}\n          data-cucumber-button=\"Submit form\"\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button=\"\"\n          {{autofocus}}\n        >\n          Submit form\n        </Ui::Button>\n\n        {{ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          isDisabled=(not this.enableSubmit)\n          type=\"submit\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{#ui/button\n          data-cucumber-button=\"Submit form\"\n          onclick=onSubmit\n          isDisabled=(not this.enableSubmit)\n          type=\"submit\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n        }}\n          Submit form\n        {{/ui/button}}\n      `,\n      errors: [\n        { message: '`data-cucumber-button` must appear after `@type`' },\n        { message: '`{{on}}` must appear after `{{autofocus}}`' },\n        { message: '`data-cucumber-button` must appear after `@type`' },\n        { message: '`{{on}}` must appear after `{{autofocus}}`' },\n        { message: '`type` must appear after `isDisabled`' },\n        { message: '`type` must appear after `isDisabled`' },\n      ],\n    },\n    {\n      code: `\n        <MyComponent\n          @title=\"Update history\"\n          @description={{if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          }}\n        />\n\n        {{my-component\n          title=\"Update history\"\n          description=(if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          )\n        }}\n      `,\n      output: `\n        <MyComponent\n          @description={{if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          }}\n          @title=\"Update history\"\n        />\n\n        {{my-component\n          description=(if\n            this.someCondition\n            (t\n              \"my-component.description.version-1\"\n              packageVersion=\"6.0.0\"\n              packageName=\"ember-source\"\n              installedOn=this.installationDate\n            )\n            (t\n              \"my-component.description.version-2\"\n              (hash\n                installedOn=this.installationDate\n                packageVersion=\"6.0.0\"\n                packageName=\"ember-source\"\n              )\n            )\n          )\n          title=\"Update history\"\n        }}\n      `,\n      errors: [\n        { message: '`@title` must appear after `@description`' },\n        { message: '`packageVersion` must appear after `packageName`' },\n        { message: '`packageVersion` must appear after `packageName`' },\n        { message: '`title` must appear after `description`' },\n        { message: '`packageVersion` must appear after `packageName`' },\n        { message: '`packageVersion` must appear after `packageName`' },\n      ],\n    },\n    {\n      code: `\n        <Ui::Button\n          data-cucumber-button=\"Submit form\"\n          {{! @glint-expect-error: @onSubmit has incorrect type }}\n          {{on \"click\" @onSubmit}}\n          @type=\"submit\"\n          {{!-- @glint-expect-error: this.enableSubmit has incorrect type --}}\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n      `,\n      output: `\n        <Ui::Button\n          @type=\"submit\"\n          {{! @glint-expect-error: @onSubmit has incorrect type }}\n          {{on \"click\" @onSubmit}}\n          data-cucumber-button=\"Submit form\"\n          {{!-- @glint-expect-error: this.enableSubmit has incorrect type --}}\n          @isDisabled={{not this.enableSubmit}}\n          class={{local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          }}\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n          {{autofocus}}\n        />\n      `,\n      errors: [\n        { message: '`data-cucumber-button` must appear after `@type`' },\n        { message: '`{{on}}` must appear after `{{autofocus}}`' },\n      ],\n    },\n    {\n      code: `\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button\n          @label=\"Submit form\"\n        />\n\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          ...attributes\n          data-test-button\n        >\n          Submit form\n        </this.MyButton>\n      `,\n      output: `\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button\n          ...attributes\n          @label=\"Submit form\"\n        />\n\n        <this.MyButton\n          {{on \"click\" this.doSomething}}\n          @type=\"submit\"\n          data-test-button\n          ...attributes\n        >\n          Submit form\n        </this.MyButton>\n      `,\n      errors: [\n        { message: '`...attributes` must appear after `data-test-button`' },\n        { message: '`...attributes` must appear after `data-test-button`' },\n      ],\n    },\n    {\n      code: `\n        {{component \"ui/button\"}}\n\n        {{component \"ui/button\"\n          onClick=this.doSomething\n          type=\"submit\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=this.doSomething\n          type=\"submit\"\n          isDisabled=true\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=@onSubmit\n          type=\"submit\"\n          isDisabled=(not this.enableSubmit)\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n      `,\n      output: `\n        {{component \"ui/button\"}}\n\n        {{component \"ui/button\"\n          onClick=this.doSomething\n          data-test-button=\"\"\n          type=\"submit\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=this.doSomething\n          isDisabled=true\n          type=\"submit\"\n          class=\"ui-button disabled\"\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n\n        {{component \"ui/button\"\n          data-cucumber-button=\"Submit form\"\n          onClick=@onSubmit\n          isDisabled=(not this.enableSubmit)\n          type=\"submit\"\n          class=(local\n            this.styles\n            \"button\"\n            (unless this.enableSubmit \"disabled\")\n          )\n          data-test-button=\"\"\n          label=\"Submit form\"\n        }}\n      `,\n      errors: [\n        { message: '`type` must appear after `data-test-button`' },\n        { message: '`type` must appear after `isDisabled`' },\n        { message: '`type` must appear after `isDisabled`' },\n      ],\n    },\n    {\n      code: `\n        <button\n          data-cucumber-button=\"Submit form\"\n          {{on \"click\" @onSubmit}}\n          type=\"submit\"\n          disabled\n          class={{local this.styles \"button\" \"disabled\"}}\n          ...attributes\n          data-test-button\n          {{autofocus}}\n        >\n          Submit form\n        </button>\n\n        <div\n          role=\"button\"\n          {{on \"mouseleave\" (fn this.setFocus false)}}\n          class={{local\n            this.styles\n            \"button\"\n            (if this.isFocused \"focused\")\n          }}\n          {{on \"click\" this.trackEvent}}\n          {{on \"mouseenter\" (fn this.setFocus true)}}\n          {{on \"click\" this.submitForm}}\n        >\n          Submit form\n        </div>\n      `,\n      output: `\n        <button\n          data-cucumber-button=\"Submit form\"\n          {{autofocus}}\n          type=\"submit\"\n          disabled\n          class={{local this.styles \"button\" \"disabled\"}}\n          ...attributes\n          data-test-button\n          {{on \"click\" @onSubmit}}\n        >\n          Submit form\n        </button>\n\n        <div\n          class={{local\n            this.styles\n            \"button\"\n            (if this.isFocused \"focused\")\n          }}\n          {{on \"mouseleave\" (fn this.setFocus false)}}\n          role=\"button\"\n          {{on \"click\" this.trackEvent}}\n          {{on \"mouseenter\" (fn this.setFocus true)}}\n          {{on \"click\" this.submitForm}}\n        >\n          Submit form\n        </div>\n      `,\n      errors: [\n        { message: '`{{on}}` must appear after `{{autofocus}}`' },\n        { message: '`type` must appear after `disabled`' },\n        { message: '`role` must appear after `class`' },\n        { message: '`{{on}}` must appear after `{{on}}`' },\n      ],\n    },\n    {\n      code: `\n        {{#let (unique-id) as |formId|}}\n          <form\n            class={{this.styles.form}}\n            data-test-form={{if @title @title \"\"}}\n            aria-labelledby={{if @title (concat formId \"-title\")}}\n            aria-describedby={{if\n              @instructions\n              (concat formId \"-instructions\")\n            }}\n            {{autofocus}}\n            {{on \"submit\" this.submitForm}}\n          >\n            <Ui::Form::Information\n              @formId={{formId}}\n              @title={{@title}}\n              @instructions={{@instructions}}\n            />\n\n            <ContainerQuery\n              @features={{hash wide=(width min=480)}}\n              as |CQ|\n            >\n              {{yield\n                (hash\n                  Input=(component\n                    \"ui/form/input\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Textarea=(component\n                    \"ui/form/textarea\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Number=(component\n                    \"ui/form/number\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Checkbox=(component\n                    \"ui/form/checkbox\"\n                    changeset=this.changeset\n                    isInline=true\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Select=(component\n                    \"ui/form/select\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                )\n              }}\n            </ContainerQuery>\n\n            <div class={{this.styles.actions}}>\n              <button\n                type=\"submit\"\n                data-test-button=\"Submit\"\n                class={{this.styles.submit-button}}\n              >\n                {{t \"components.ui.form.submit\"}}\n              </button>\n            </div>\n          </form>\n        {{/let}}\n      `,\n      output: `\n        {{#let (unique-id) as |formId|}}\n          <form\n            class={{this.styles.form}}\n            aria-labelledby={{if @title (concat formId \"-title\")}}\n            data-test-form={{if @title @title \"\"}}\n            aria-describedby={{if\n              @instructions\n              (concat formId \"-instructions\")\n            }}\n            {{autofocus}}\n            {{on \"submit\" this.submitForm}}\n          >\n            <Ui::Form::Information\n              @formId={{formId}}\n              @instructions={{@instructions}}\n              @title={{@title}}\n            />\n\n            <ContainerQuery\n              @features={{hash wide=(width min=480)}}\n              as |CQ|\n            >\n              {{yield\n                (hash\n                  Input=(component\n                    \"ui/form/input\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Number=(component\n                    \"ui/form/number\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Textarea=(component\n                    \"ui/form/textarea\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Checkbox=(component\n                    \"ui/form/checkbox\"\n                    changeset=this.changeset\n                    isInline=true\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                  Select=(component\n                    \"ui/form/select\"\n                    changeset=this.changeset\n                    isWide=CQ.features.wide\n                    onUpdate=this.updateChangeset\n                  )\n                )\n              }}\n            </ContainerQuery>\n\n            <div class={{this.styles.actions}}>\n              <button\n                data-test-button=\"Submit\"\n                type=\"submit\"\n                class={{this.styles.submit-button}}\n              >\n                {{t \"components.ui.form.submit\"}}\n              </button>\n            </div>\n          </form>\n        {{/let}}\n      `,\n      errors: [\n        { message: '`data-test-form` must appear after `aria-labelledby`' },\n        { message: '`@title` must appear after `@instructions`' },\n        { message: '`Textarea` must appear after `Number`' },\n        { message: '`type` must appear after `data-test-button`' },\n      ],\n    },\n    {\n      code: `\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          ...attributes\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          ...attributes\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n      `,\n      output: `\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          ...attributes\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n\n        <iframe\n          {{did-insert this.doSomething1}}\n          {{on \"load\" this.doSomething2}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          ...attributes\n        ></iframe>\n\n        <iframe\n          class=\"full-screen\"\n          data-test-id=\"my-iframe\"\n          id={{@id}}\n          src={{this.url}}\n          {{did-insert this.doSomething1}}\n          ...attributes\n          {{on \"load\" this.doSomething2}}\n        ></iframe>\n      `,\n      errors: [\n        { message: '`...attributes` must appear after `modifiers`' },\n        { message: '`...attributes` must appear after modifiers' },\n      ],\n    },\n    {\n      code: `\n        <Ui::Page\n          @title={{\"Your product\"}}\n          {{! @glint-expect-error: Type 'string | null' is not assignable to type 'string'. }}\n          @routeName={{this.router.currentRouteName}}\n          as |Page|\n        >\n          {{outlet}}\n\n          {{#if this.someCondition1}}\n            <Page.Button @id=\"products.overview\" @icon=\"rightarrow\" @label=\"\" />\n          {{else if this.someCondition2}}\n            <Page.Button @id=\"products.product\" @icon=\"\" @label=\"\" />\n          {{else}}\n            <Page.Button\n              @id=\"products.product\"\n              @icon=\"\"\n              @label=\"\n              \"\n            />\n          {{/if}}\n        </Ui::Page>\n      `,\n      output: `\n        <Ui::Page\n          @routeName={{this.router.currentRouteName}}\n          {{! @glint-expect-error: Type 'string | null' is not assignable to type 'string'. }}\n          @title={{\"Your product\"}}\n          as |Page|\n        >\n          {{outlet}}\n\n          {{#if this.someCondition1}}\n            <Page.Button @icon=\"rightarrow\" @id=\"products.overview\" @label=\"\" />\n          {{else if this.someCondition2}}\n            <Page.Button @icon=\"\" @id=\"products.product\" @label=\"\" />\n          {{else}}\n            <Page.Button\n              @icon=\"\"\n              @id=\"products.product\"\n              @label=\"\n              \"\n            />\n          {{/if}}\n        </Ui::Page>\n      `,\n      errors: [\n        { message: '`@title` must appear after `@routeName`' },\n        { message: '`@id` must appear after `@icon`' },\n        { message: '`@id` must appear after `@icon`' },\n        { message: '`@id` must appear after `@icon`' },\n      ],\n    },\n    {\n      code: `\n        <MyComponent\n          @parentContainerId={{concat \"#\" @parentId}}\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          @style={{concat \".\" @type \"1\"}}\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          @className={{concat \"a\" @typeA \"b\" @typeB \"c\" @typeC \"d\"}}\n          aria-describedby=\"1\"\n        />\n\n        <input\n          type=\"tel\"\n          local-class=\"input {{concat 'flag-' @country}}\"\n        />\n\n        <MyComponent\n          @parentContainerId=\"#{{@parentId}}\"\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          @style=\".{{@type}}1\"\n          @isOpen={{this.isOpen}}\n        />\n\n        <MyComponent\n          aria-describedby=\"1\"\n          @className=\"a{{@typeA}}b{{@typeB}}c{{@typeC}}d\"\n        />\n\n        <input\n          type=\"tel\"\n          local-class=\"input flag-{{@country}}\"\n        />\n      `,\n      output: `\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @parentContainerId={{concat \"#\" @parentId}}\n        />\n\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @style={{concat \".\" @type \"1\"}}\n        />\n\n        <MyComponent\n          @className={{concat \"a\" @typeA \"b\" @typeB \"c\" @typeC \"d\"}}\n          aria-describedby=\"1\"\n        />\n\n        <input\n          local-class=\"input {{concat 'flag-' @country}}\"\n          type=\"tel\"\n        />\n\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @parentContainerId=\"#{{@parentId}}\"\n        />\n\n        <MyComponent\n          @isOpen={{this.isOpen}}\n          @style=\".{{@type}}1\"\n        />\n\n        <MyComponent\n          @className=\"a{{@typeA}}b{{@typeB}}c{{@typeC}}d\"\n          aria-describedby=\"1\"\n        />\n\n        <input\n          local-class=\"input flag-{{@country}}\"\n          type=\"tel\"\n        />\n      `,\n      errors: [\n        { message: '`@parentContainerId` must appear after `@isOpen`' },\n        { message: '`@style` must appear after `@isOpen`' },\n        { message: '`type` must appear after `local-class`' },\n        { message: '`@parentContainerId` must appear after `@isOpen`' },\n        { message: '`@style` must appear after `@isOpen`' },\n        { message: '`aria-describedby` must appear after `@className`' },\n        { message: '`type` must appear after `local-class`' },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-splat-attributes-only.js",
    "content": "const rule = require('../../../lib/rules/template-splat-attributes-only');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-splat-attributes-only', rule, {\n  valid: [\n    '<template><div ...attributes></div></template>',\n    '<template><MyComponent ...attributes /></template>',\n\n    '<template><div attributes></div></template>',\n    '<template><div arguments></div></template>',\n    '<template><div><div ...attributes></div></div></template>',\n  ],\n  invalid: [\n    {\n      code: '<template><div ...props></div></template>',\n      output: null,\n      errors: [{ messageId: 'onlyAttributes' }],\n    },\n\n    {\n      code: '<template><div ...arguments></div></template>',\n      output: null,\n      errors: [{ messageId: 'onlyAttributes' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-splat-attributes-only', rule, {\n  valid: [\n    '<div ...attributes></div>',\n    '<div attributes></div>',\n    '<div arguments></div>',\n    '<div><div ...attributes></div></div>',\n  ],\n  invalid: [\n    {\n      code: '<div ...arguments></div>',\n      output: null,\n      errors: [{ message: 'Only `...attributes` can be applied to elements' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-style-concatenation.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-style-concatenation');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-style-concatenation', rule, {\n  valid: [\n    `<template>\n      <div style=\"color: red;\">Content</div>\n    </template>`,\n    `<template>\n      <div style={{this.computedStyle}}>Content</div>\n    </template>`,\n    `<template>\n      <div style={{html-safe this.styleString}}>Content</div>\n    </template>`,\n\n    '<template><img></template>',\n    '<template><img style={{myStyle}}></template>',\n    '<template><img style={{background-image url}}></template>',\n    '<template><img style=\"background-image: url(/foo.png)\"}}></template>',\n    '<template><img style={{html-safe (concat \"background-image: url(\" url \")\")}}></template>',\n    '<template><img style={{html-safe (concat knownSafeStyle1 \";\" knownSafeStyle2)}}></template>',\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <div style=\"color: {{this.color}};\">Content</div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n    {\n      code: `<template>\n        <div style={{concat \"width: \" this.width \"px;\"}}>Content</div>\n      </template>`,\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n          type: 'GlimmerAttrNode',\n        },\n      ],\n    },\n\n    {\n      code: '<template><img style=\"{{myStyle}}\"></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n    {\n      code: '<template><img style=\"background-image: {{url}}\"></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n    {\n      code: '<template><img style=\"{{background-image url}}\"></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n    {\n      code: '<template><img style={{concat knownSafeStyle1 \";\" knownSafeStyle2}}></template>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-style-concatenation', rule, {\n  valid: [\n    '<img>',\n    '<img style={{myStyle}}>',\n    '<img style={{background-image url}}>',\n    '<img style=\"background-image: url(/foo.png)\"}}>',\n    '<img style={{html-safe (concat \"background-image: url(\" url \")\")}}>',\n    '<img style={{html-safe (concat knownSafeStyle1 \";\" knownSafeStyle2)}}>',\n  ],\n  invalid: [\n    {\n      code: '<img style=\"{{myStyle}}\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n    {\n      code: '<img style=\"background-image: {{url}}\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n    {\n      code: '<img style=\"{{background-image url}}\">',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n    {\n      code: '<img style={{concat knownSafeStyle1 \";\" knownSafeStyle2}}>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Avoid string concatenation in style attributes. Use a computed property with htmlSafe instead.',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-table-groups.js",
    "content": "//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/template-table-groups');\nconst RuleTester = require('eslint').RuleTester;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-table-groups', rule, {\n  valid: [\n    `<template>\n      <table>\n        <thead>\n          <tr><th>Header</th></tr>\n        </thead>\n        <tbody>\n          <tr><td>Data</td></tr>\n        </tbody>\n      </table>\n    </template>`,\n    `<template>\n      <table>\n        <tbody>\n          <tr><td>Data</td></tr>\n        </tbody>\n      </table>\n    </template>`,\n    `<template>\n      <table>\n        <caption>My Table</caption>\n      </table>\n    </template>`,\n    `<template>\n      <div>Not a table</div>\n    </template>`,\n\n    `<template>\n    <table>\n    {{#if showCaption}}\n      <caption>Some Name</caption>\n    {{/if}}\n    <colgroup></colgroup>\n    {{#if foo}}\n      <thead>\n        <tr></tr>\n      </thead>\n    {{else}}\n      <tbody>\n        <tr></tr>\n      </tbody>\n    {{/if}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#if foo}}\n      <tfoot>\n        <tr></tr>\n      </tfoot>\n    {{/if}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#unless foo}}\n      <tfoot>\n        <tr></tr>\n      </tfoot>\n    {{/unless}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#each foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/each}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#each-in foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/each-in}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#let foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/let}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#with foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/with}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n    {{#each foo as |bar|}}\n      {{#if bar}}\n        {{#unless baz}}\n          <tfoot>\n            <tr>bar</tr>\n          </tfoot>\n        {{/unless}}\n      {{/if}}\n    {{/each}}\n    </table>\n    </template>`,\n    '<template><table>{{some-component tagName=\"tbody\"}}</table></template>',\n    '<template><table>{{some-component tagName=\"thead\"}}</table></template>',\n    '<template><table>{{some-component tagName=\"tfoot\"}}</table></template>',\n    '<template><table>{{#some-component tagName=\"tbody\"}}{{/some-component}}</table></template>',\n    '<template><table>{{#some-component tagName=\"thead\"}}{{/some-component}}</table></template>',\n    '<template><table>{{#some-component tagName=\"tfoot\"}}{{/some-component}}</table></template>',\n    '<template><table>{{component \"some-component\" tagName=\"tbody\"}}</table></template>',\n    '<template><table>{{component \"some-component\" tagName=\"thead\"}}</table></template>',\n    '<template><table>{{component \"some-component\" tagName=\"tfoot\"}}</table></template>',\n    '<template><table><SomeComponent @tagName=\"tbody\" /></table></template>',\n    '<template><table><SomeComponent @tagName=\"thead\" /></table></template>',\n    '<template><table><SomeComponent @tagName=\"tfoot\" /></table></template>',\n    '<template><table><SomeComponent @tagName=\"tbody\"></SomeComponent></table></template>',\n    '<template><table><SomeComponent @tagName=\"thead\"></SomeComponent></table></template>',\n    '<template><table><SomeComponent @tagName=\"tfoot\"></SomeComponent></table></template>',\n    '<template> <table>{{yield}}</table> </template>',\n    '<template><table><!-- this --></table></template>',\n    '<template><table>{{! or this }}</table></template>',\n    '<template><table> </table></template>',\n    '<template><table></table></template>',\n    '<template><table> <caption>Foo</caption></table></template>',\n    '<template><table><colgroup><col style=\"background-color: red\"></colgroup></table></template>',\n    '<template><table><thead><tr><td>Header</td></tr></thead></table></template>',\n    '<template><table><tbody><tr><td>Body</td></tr></tbody></table></template>',\n    '<template><table><tfoot><tr><td>Footer</td></tr></tfoot></table></template>',\n    '<template><table>{{! this is a comment }}<thead><tr><td>Header</td></tr></thead><tbody><tr><td>Body</td></tr></tbody><tfoot><tr><td>Footer</td></tr></tfoot></table></template>',\n    '<template><table><colgroup><col style=\"background-color: red\"></colgroup><tbody><tr><td>Body</td></tr></tbody></table></template>',\n    '<template><table><tbody></tbody></table></template>',\n    '<template><table><colgroup></colgroup><colgroup></colgroup><tbody></tbody></table></template>',\n    `<template>\n    <table>\n      {{#if someCondition}}\n        <thead />\n        <tbody />\n      {{else}}\n        <caption />\n        <thead />\n        <tbody />\n      {{/if}}\n    </table>\n    </template>`,\n    `<template>\n    <table>\n      {{#unless someCondition}}\n        <thead />\n        <tbody />\n      {{else}}\n        <caption />\n        <thead />\n        <tbody />\n      {{/unless}}\n    </table>\n    </template>`,\n  ],\n\n  invalid: [\n    {\n      code: `<template>\n        <table>\n          <tr><td>Data</td></tr>\n        </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n        <table>\n          <tr><th>Header</th></tr>\n          <tr><td>Data</td></tr>\n        </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n\n    {\n      code: `<template>\n      <table>\n      {{#if showCaption}}\n        <thead>Some Name</thead>\n      {{/if}}\n      {{#if foo}}\n        <span>12</span>\n      {{else}}\n        <p>text</p>\n      {{/if}}\n      <colgroup></colgroup>\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n      <table>\n      {{#if showCaption}}\n        <div>Some Name</div>\n      {{/if}}\n      {{#if foo}}\n        <span>12</span>\n      {{else}}\n        <p>text</p>\n      {{/if}}\n      <colgroup></colgroup>\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n      <table>\n      {{#if foo}}\n        {{else}}\n        <div></div>\n      {{/if}}\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n      <table>\n      {{#unless foo}}\n        <div>\n          <tr></tr>\n        </div>\n      {{/unless}}\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n      <table>\n      {{#if foo}}\n        <div>\n          <tr></tr>\n        </div>\n      {{/if}}\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n      <table>\n      {{#unless foo}}\n        {{some-component}}\n      {{/unless}}\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: `<template>\n      <table>\n      {{#something foo}}\n        <tbody></tbody>\n      {{/something}}\n      </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table><tr><td>Foo</td></tr></table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table>{{some-component}}</table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table>{{#each foo as |bar|}}{{bar}}{{/each}}</table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table> whitespace<thead></thead></table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table>{{some-component tagName=\"div\"}}</table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table>{{some-component otherProp=\"tbody\"}}</table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table><SomeComponent @tagName=\"div\" /></table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table><SomeComponent @otherProp=\"tbody\" /></table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table>some text</table></template>',\n      output: null,\n      errors: [{ messageId: 'missing' }],\n    },\n    {\n      code: '<template><table><tfoot /><thead /></table></template>',\n      output: null,\n      errors: [{ messageId: 'ordering' }],\n    },\n    {\n      code: '<template><table><tbody /><caption /></table></template>',\n      output: null,\n      errors: [{ messageId: 'ordering' }],\n    },\n    {\n      code: `<template>\n        <table>\n          <tbody />\n          <colgroup />\n        </table>\n      </template>`,\n      output: null,\n      errors: [{ messageId: 'ordering' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-table-groups', rule, {\n  valid: [\n    `\n    <table>\n    {{#if showCaption}}\n      <caption>Some Name</caption>\n    {{/if}}\n    <colgroup></colgroup>\n    {{#if foo}}\n      <thead>\n        <tr></tr>\n      </thead>\n    {{else}}\n      <tbody>\n        <tr></tr>\n      </tbody>\n    {{/if}}\n    </table>\n    `,\n    `\n    <table>\n    {{#if foo}}\n      <tfoot>\n        <tr></tr>\n      </tfoot>\n    {{/if}}\n    </table>\n    `,\n    `\n    <table>\n    {{#unless foo}}\n      <tfoot>\n        <tr></tr>\n      </tfoot>\n    {{/unless}}\n    </table>\n    `,\n    `\n    <table>\n    {{#each foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/each}}\n    </table>\n    `,\n    `\n    <table>\n    {{#each-in foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/each-in}}\n    </table>\n    `,\n    `\n    <table>\n    {{#let foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/let}}\n    </table>\n    `,\n    `\n    <table>\n    {{#with foo as |bar|}}\n      <tfoot>\n        <tr>bar</tr>\n      </tfoot>\n    {{/with}}\n    </table>\n    `,\n    `\n    <table>\n    {{#each foo as |bar|}}\n      {{#if bar}}\n        {{#unless baz}}\n          <tfoot>\n            <tr>bar</tr>\n          </tfoot>\n        {{/unless}}\n      {{/if}}\n    {{/each}}\n    </table>\n    `,\n    '<table>{{some-component tagName=\"tbody\"}}</table>',\n    '<table>{{some-component tagName=\"thead\"}}</table>',\n    '<table>{{some-component tagName=\"tfoot\"}}</table>',\n    '<table>{{#some-component tagName=\"tbody\"}}{{/some-component}}</table>',\n    '<table>{{#some-component tagName=\"thead\"}}{{/some-component}}</table>',\n    '<table>{{#some-component tagName=\"tfoot\"}}{{/some-component}}</table>',\n    '<table>{{component \"some-component\" tagName=\"tbody\"}}</table>',\n    '<table>{{component \"some-component\" tagName=\"thead\"}}</table>',\n    '<table>{{component \"some-component\" tagName=\"tfoot\"}}</table>',\n    '<table><SomeComponent @tagName=\"tbody\" /></table>',\n    '<table><SomeComponent @tagName=\"thead\" /></table>',\n    '<table><SomeComponent @tagName=\"tfoot\" /></table>',\n    '<table><SomeComponent @tagName=\"tbody\"></SomeComponent></table>',\n    '<table><SomeComponent @tagName=\"thead\"></SomeComponent></table>',\n    '<table><SomeComponent @tagName=\"tfoot\"></SomeComponent></table>',\n    ' <table>{{yield}}</table> ',\n    '<table><!-- this --></table>',\n    '<table>{{! or this }}</table>',\n    '<table> </table>',\n    '<table> <caption>Foo</caption></table>',\n    '<table><colgroup><col style=\"background-color: red\"></colgroup></table>',\n    '<table><thead><tr><td>Header</td></tr></thead></table>',\n    '<table><tbody><tr><td>Body</td></tr></tbody></table>',\n    '<table><tfoot><tr><td>Footer</td></tr></tfoot></table>',\n    '{{! this is a comment }}',\n    '<tr><td>Header</td></tr>',\n    '<tr><td>Body</td></tr>',\n    '<tr><td>Footer</td></tr>',\n    '<col style=\"background-color: red\">',\n    '<table><colgroup></colgroup><colgroup></colgroup><tbody></tbody></table>',\n    `\n    <table>\n      {{#if someCondition}}\n        <thead />\n        <tbody />\n      {{else}}\n        <caption />\n        <thead />\n        <tbody />\n      {{/if}}\n    </table>\n    `,\n    `\n    <table>\n      {{#unless someCondition}}\n        <thead />\n        <tbody />\n      {{else}}\n        <caption />\n        <thead />\n        <tbody />\n      {{/unless}}\n    </table>\n    `,\n    `\n        <StickyTable>\n          <thead></thead>\n          <tbody></tbody>\n        </StickyTable>\n        <MyThing @tagName=\"table\">\n          <thead></thead>\n          <tbody></tbody>\n        </MyThing>\n      `,\n    '<table><tbody></tbody></table>',\n    // allowed-*-components config tests (curly-brace invocation)\n    {\n      code: `\n      <table>\n        {{nested/my-caption}}\n        {{nested/my-colgroup}}\n        {{nested/my-thead}}\n        {{nested/my-tbody}}\n        {{nested/my-tfoot}}\n      </table>\n      `,\n      options: [\n        {\n          'allowed-caption-components': ['nested/my-caption'],\n          'allowed-colgroup-components': ['nested/my-colgroup'],\n          'allowed-thead-components': ['nested/my-thead'],\n          'allowed-tbody-components': ['nested/my-tbody'],\n          'allowed-tfoot-components': ['nested/my-tfoot'],\n        },\n      ],\n    },\n    // allowed-*-components config tests (angle-bracket invocation)\n    {\n      code: `\n      <table>\n        <Nested::MyCaption />\n        <Nested::MyColgroup />\n        <Nested::MyThead />\n        <Nested::MyTbody />\n        <Nested::MyTfoot />\n      </table>\n      `,\n      options: [\n        {\n          'allowed-caption-components': ['nested/my-caption'],\n          'allowed-colgroup-components': ['nested/my-colgroup'],\n          'allowed-thead-components': ['nested/my-thead'],\n          'allowed-tbody-components': ['nested/my-tbody'],\n          'allowed-tfoot-components': ['nested/my-tfoot'],\n        },\n      ],\n    },\n    {\n      code: `\n      <table>\n        <Nested::HeadOrFoot />\n        <Nested::Body />\n        <Nested::HeadOrFoot/>\n      </table>\n      `,\n      options: [\n        {\n          'allowed-thead-components': ['nested/head-or-foot'],\n          'allowed-tbody-components': ['nested/body'],\n          'allowed-tfoot-components': ['nested/head-or-foot'],\n        },\n      ],\n    },\n    {\n      code: `\n      <table>\n        <Nested::MyCaption />\n        <thead />\n        <Nested::MyCaption @tagName=\"tbody\" />\n        <tfoot />\n      </table>\n      `,\n      options: [\n        {\n          'allowed-caption-components': ['nested/my-caption'],\n        },\n      ],\n    },\n  ],\n  invalid: [\n    {\n      code: `\n      <table>\n      {{#if showCaption}}\n        <thead>Some Name</thead>\n      {{/if}}\n      {{#if foo}}\n        <span>12</span>\n      {{else}}\n        <p>text</p>\n      {{/if}}\n      <colgroup></colgroup>\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n      {{#if showCaption}}\n        <div>Some Name</div>\n      {{/if}}\n      {{#if foo}}\n        <span>12</span>\n      {{else}}\n        <p>text</p>\n      {{/if}}\n      <colgroup></colgroup>\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n      {{#if foo}}\n        {{else}}\n        <div></div>\n      {{/if}}\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n      {{#unless foo}}\n        <div>\n          <tr></tr>\n        </div>\n      {{/unless}}\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n      {{#if foo}}\n        <div>\n          <tr></tr>\n        </div>\n      {{/if}}\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n      {{#unless foo}}\n        {{some-component}}\n      {{/unless}}\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n      {{#something foo}}\n        <tbody></tbody>\n      {{/something}}\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table><tr><td>Foo</td></tr></table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table><tr></tr><tbody><tr><td>Foo</td></tr></tbody></table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table>{{some-component}}</table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table>{{#each foo as |bar|}}{{bar}}{{/each}}</table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table> whitespace<thead></thead></table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table>{{some-component tagName=\"div\"}}</table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table>{{some-component otherProp=\"tbody\"}}</table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table><SomeComponent @tagName=\"div\" /></table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table><SomeComponent @otherProp=\"tbody\" /></table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table>some text</table>',\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: '<table><tfoot /><thead /></table>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n        },\n      ],\n    },\n    {\n      code: '<table><tbody /><caption /></table>',\n      output: null,\n      errors: [\n        {\n          message:\n            'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n        },\n      ],\n    },\n    {\n      code: `\n        <table>\n          <tbody />\n          <colgroup />\n        </table>\n      `,\n      output: null,\n      errors: [\n        {\n          message:\n            'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n        },\n      ],\n    },\n    {\n      code: `\n      <table>\n        <Nested::SomethingElse />\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n        <Nested::MyTfoot />\n        <Nested::MyThead />\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n        <Nested::HeadOrFoot />\n        <Nested::Body />\n        <Nested::HeadOrFoot/>\n        <Nested::Body />\n      </table>\n      `,\n      output: null,\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n        <thead />\n        <Nested::MyTbody @tagName=\"caption\" />\n        <tbody />\n      </table>\n      `,\n      output: null,\n      options: [\n        {\n          'allowed-tbody-components': ['nested/my-tbody'],\n        },\n      ],\n      errors: [\n        {\n          message:\n            'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n        },\n      ],\n    },\n    // Config: allowed-*-components invalid tests\n    {\n      code: `\n      <table>\n        <Nested::SomethingElse />\n      </table>\n      `,\n      output: null,\n      options: [{ 'allowed-caption-components': ['nested/allowed'] }],\n      errors: [{ message: 'Tables must have a table group (thead, tbody or tfoot).' }],\n    },\n    {\n      code: `\n      <table>\n        <Nested::MyTfoot />\n        <Nested::MyThead />\n      </table>\n      `,\n      output: null,\n      options: [\n        {\n          'allowed-thead-components': ['nested/my-thead'],\n          'allowed-tfoot-components': ['nested/my-tfoot'],\n        },\n      ],\n      errors: [\n        {\n          message:\n            'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n        },\n      ],\n    },\n    {\n      code: `\n      <table>\n        <Nested::HeadOrFoot />\n        <Nested::Body />\n        <Nested::HeadOrFoot/>\n        <Nested::Body />\n      </table>\n      `,\n      output: null,\n      options: [\n        {\n          'allowed-thead-components': ['nested/head-or-foot'],\n          'allowed-tbody-components': ['nested/body'],\n          'allowed-tfoot-components': ['nested/head-or-foot'],\n        },\n      ],\n      errors: [\n        {\n          message:\n            'Tables must have table groups in the correct order (caption, colgroup, thead, tbody then tfoot).',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/template-template-length.js",
    "content": "const rule = require('../../../lib/rules/template-template-length');\nconst RuleTester = require('eslint').RuleTester;\n\nconst ruleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser'),\n  parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n});\n\nruleTester.run('template-template-length', rule, {\n  valid: [\n    {\n      code: `<template>\n  one\n  two\n</template>`,\n      options: [{ max: 5 }],\n    },\n    {\n      code: `<template>\n  one\n  two\n  three\n</template>`,\n      options: [{ min: 3 }],\n    },\n    {\n      code: `<template>\n  one\n</template>`,\n      options: [false],\n    },\n\n    `<template>testing this\nand\nthis\nand\this</template>`,\n  ],\n  invalid: [\n    {\n      code: `<template>\n  one\n  two\n</template>`,\n      output: null,\n      options: [{ min: 10 }],\n      errors: [{ message: 'Template length of 4 is smaller than 10' }],\n    },\n    {\n      code: `<template>\n  one\n  two\n  three\n</template>`,\n      output: null,\n      options: [{ max: 3 }],\n      errors: [{ message: 'Template length of 5 exceeds 3' }],\n    },\n  ],\n});\n\nconst hbsRuleTester = new RuleTester({\n  parser: require.resolve('ember-eslint-parser/hbs'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n  },\n});\n\nhbsRuleTester.run('template-template-length', rule, {\n  valid: [\n    `testing this\nand\nthis\nand\this`,\n    `testing\nthis\n`,\n    `testing\nthis\nand\this\n`,\n    `testing\nthis\nandthis\n`,\n    // Config: max option\n    {\n      code: 'testing\\nthis\\n',\n      options: [{ max: 10 }],\n    },\n    // Config: min option\n    {\n      code: 'testing\\nthis\\nand\\this\\n',\n      options: [{ min: 1 }],\n    },\n    // Config: min + max options\n    {\n      code: 'testing\\nthis\\nandthis\\n',\n      options: [{ min: 1, max: 5 }],\n    },\n  ],\n  invalid: [\n    {\n      code: 'testing\\ntoo-short template\\n',\n      output: null,\n      options: [{ min: 10 }],\n      errors: [{ message: 'Template length of 3 is smaller than 10' }],\n    },\n    {\n      code: 'test\\nthis\\nand\\nthis\\n',\n      output: null,\n      options: [{ max: 3 }],\n      errors: [{ message: 'Template length of 5 exceeds 3' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/use-brace-expansion.js",
    "content": "// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/use-brace-expansion');\nconst RuleTester = require('eslint').RuleTester;\nconst { addComputedImport } = require('../../helpers/test-case');\n\nconst { ERROR_MESSAGE } = rule;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester({\n  parser: require.resolve('@babel/eslint-parser'),\n  parserOptions: {\n    ecmaVersion: 2022,\n    sourceType: 'module',\n    ecmaFeatures: { legacyDecorators: true },\n  },\n});\n\neslintTester.run('use-brace-expansion', rule, {\n  valid: [\n    '{ test: computed(\"a\", \"b\", function() {}) }',\n    '{ test: computed(function() {}) }',\n    '{ test: computed(\"a.test\", \"b.test\", function() {}) }',\n    '{ test: computed(\"a.{test,test2}\", \"b\", function() {}) }',\n    '{ test: computed(\"a.{test,test2}\", \"c\", \"b\", function() {}) }',\n    '{ test: computed(\"model.a.{test,test2}\", \"model.b.{test3,test4}\", function() {}) }',\n    '{ test: computed(\"foo.bar.{name,place}\", \"foo.qux.[]\", \"foo.baz.{thing,@each.stuff}\", function() {}) }',\n    '{ test: computed.or(\"foo.bar.name\", \"foo.bar.place\") }',\n    '{ test: computed.and(\"foo.bar.name\", \"foo.bar.place\") }',\n    \"{ test: Ember.computed.filterBy('a', 'b', false) }\",\n\n    // Decorator:\n    \"class Test { @computed('a.{test1,test2}') get someProp() { return true; } }\",\n  ].map(addComputedImport),\n  invalid: [\n    {\n      code: '{ test: computed(\"foo.{name,place}\", \"foo.[]\", \"foo.{thing,@each.stuff}\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"import Ember from 'ember'; { test: Ember.computed('foo.{name,place}', 'foo.[]', 'foo.{thing,@each.stuff}', function() {}) }\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"a.test\", \"a.test2\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"a.{same,level}\", \"a.{not,nested}\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"a.b.c.d\", \"a.b.deep.props\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"a.{test,test2}\", \"c\", \"a.test3.test4\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"a.{test,test2}\", \"a.test3\", function() {}) }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: '{ test: computed(\"a.test\", \"a.test2\", function() {}).volatile() }',\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n    {\n      code: \"class Test { @computed('a.test1', 'a.test2') get someProp() { return true; } }\",\n      output: null,\n      errors: [\n        {\n          message: ERROR_MESSAGE,\n          type: 'CallExpression',\n        },\n      ],\n    },\n  ].map(addComputedImport),\n});\n"
  },
  {
    "path": "tests/lib/rules/use-ember-data-rfc-395-imports.js",
    "content": "'use strict';\n\n//------------------------------------------------------------------------------\n// Requirements\n//------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/use-ember-data-rfc-395-imports');\nconst RuleTester = require('eslint').RuleTester;\n\nconst parserOptions = { ecmaVersion: 2022, sourceType: 'module' };\n\nconst { ERROR_MESSAGE: message } = rule;\n\n//------------------------------------------------------------------------------\n// Tests\n//------------------------------------------------------------------------------\n\nconst ruleTester = new RuleTester({\n  parserOptions,\n  parser: require.resolve('@babel/eslint-parser'),\n});\nruleTester.run('use-ember-data-rfc-395-imports', rule, {\n  valid: [\n    \"import Model from '@ember-data/model';\",\n    `import Model, { attr } from '@ember-data';\n\n     export default Model.extend({\n       name: attr('string'),\n       ...foo\n     });\n    `,\n    `import LOL from 'who-knows-but-definitely-not-ember-data';\n     import Fragment from 'ember-data-model-fragments/fragment';\n\n     const { Model } = LOL;\n    `,\n    `import Model from '@ember-data/model';\n\n     export default Model.extend({\n       name: SomethingRandom.DS('string')\n     });\n    `,\n    \"import AdapterRegistry from 'ember-data/types/registries/adapter';\",\n    \"import ModelRegistry from 'ember-data/types/registries/model';\",\n    \"import SerializerRegistry from 'ember-data/types/registries/serializer';\",\n    \"import TransformRegistry from 'ember-data/types/registries/transform';\",\n    \"import Store from 'ember-data/store';\",\n  ],\n\n  invalid: [\n    {\n      code: `import DS from 'ember-data';\n\n        export default DS.Model.extend({\n          ...foo\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n        {\n          message: \"Use `import Model from '@ember-data/model';` instead of using DS.Model\",\n          type: 'Identifier',\n        },\n      ],\n    },\n    {\n      code: `import DS, { Model } from 'ember-data';\n\n        export default Model.extend({\n          name: DS.attr('string')\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n        {\n          message: \"Use `import { attr } from '@ember-data/model';` instead of using DS.attr\",\n          type: 'Identifier',\n        },\n      ],\n    },\n    {\n      code: `import DS from 'ember-data';\n        const { Model, attr } = DS;\n\n        export default Model.extend({\n          name: attr('string')\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n        {\n          message: \"Use `import Model from '@ember-data/model';` instead of using DS destructuring\",\n          type: 'Property',\n        },\n        {\n          message:\n            \"Use `import { attr } from '@ember-data/model';` instead of using DS destructuring\",\n          type: 'Property',\n        },\n      ],\n    },\n    {\n      code: `import Model from 'ember-data/model';\n\n        export default Model.extend({\n        });\n      `,\n      output: `import Model from '@ember-data/model';\n\n        export default Model.extend({\n        });\n      `,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      code: `import Model from 'ember-data/model';\n        import attr  from 'ember-data/attr';\n\n        export default Model.extend({\n          name: attr('string')\n        });\n      `,\n      output: `import Model from '@ember-data/model';\n        import { attr } from '@ember-data/model';\n\n        export default Model.extend({\n          name: attr('string')\n        });\n      `,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      code: `import Model from '@ember-data/model';\n        import attr from 'ember-data/attr';\n\n        export default Model.extend({\n          name: attr('string')\n        });\n      `,\n      output: `import Model from '@ember-data/model';\n        import { attr } from '@ember-data/model';\n\n        export default Model.extend({\n          name: attr('string')\n        });\n      `,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      code: `import Model from '@ember-data/model';\n        import namedAttr from 'ember-data/attr';\n\n        export default Model.extend({\n          name: namedAttr('string')\n        });\n      `,\n      output: `import Model from '@ember-data/model';\n        import { attr as namedAttr } from '@ember-data/model';\n\n        export default Model.extend({\n          name: namedAttr('string')\n        });\n      `,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      code: `import { Model } from 'ember-data';\n\n        export default Model.extend({\n        });\n      `,\n      output: null,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n      ],\n    },\n    {\n      code: `import ED from 'ember-data';\n\n        const { Model } = ED;\n      `,\n      output: null,\n      errors: [\n        {\n          message,\n          type: 'ImportDeclaration',\n        },\n        {\n          message: \"Use `import Model from '@ember-data/model';` instead of using DS destructuring\",\n          type: 'Property',\n        },\n      ],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules/use-ember-get-and-set.js",
    "content": "const path = require('path');\n\n// ------------------------------------------------------------------------------\n// Requirements\n// ------------------------------------------------------------------------------\n\nconst rule = require('../../../lib/rules/use-ember-get-and-set');\nconst RuleTester = require('eslint').RuleTester;\n\n// ------------------------------------------------------------------------------\n// Tests\n// ------------------------------------------------------------------------------\n\nconst eslintTester = new RuleTester();\neslintTester.run('use-ember-get-and-set', rule, {\n  valid: [\n    'get(this, \"test\")',\n    'get(controller, \"test\")',\n    'set(this, \"test\", someValue)',\n    'set(controller, \"test\", someValue)',\n    'getProperties(this, name, email, password)',\n    'getProperties(controller, name, email, password)',\n    'setProperties(this, {name: \"Jon\", email: \"jon@snow.com\"})',\n    'setProperties(controller, {name: \"Jon\", email: \"jon@snow.com\"})',\n    'getWithDefault(this, \"test\", \"default\")',\n    'getWithDefault(controller, \"test\", \"default\")',\n    {\n      filename: path.join('app', 'tests', 'unit', 'components', 'component-test.js'),\n      code: 'this.get(\"myProperty\")',\n    },\n    {\n      filename: path.join('app', 'tests', 'unit', 'components', 'component-test.js'),\n      code: 'this.set(\"myProperty\", \"value\")',\n    },\n    {\n      filename: path.join('app', 'mirage', 'config.js'),\n      code: 'this.get(\"/resources\")',\n    },\n    {\n      code: 'import Ember from \"ember\"; Ember.get(this, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: 'import Ember from \"ember\"; Ember.set(this, \"test\", someValue)',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: 'import EmberAlias from \"ember\"; EmberAlias.get(this, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n\n    // ignoreNonThisExpressions\n    {\n      code: \"let a = new Map(); a.set('name', 'Tomster');\",\n      options: [{ ignoreNonThisExpressions: true }],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: \"let a = new Map(); a.get('myKey')\",\n      options: [{ ignoreNonThisExpressions: true }],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n    {\n      code: 'this.test(\"ok\")',\n      options: [{ ignoreNonThisExpressions: true }],\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n    },\n\n    // ignoreThisExpressions\n    {\n      code: 'this.get(\"test\")',\n      options: [{ ignoreThisExpressions: true }],\n    },\n  ],\n  invalid: [\n    // Non-fixable errors\n    {\n      code: 'this.get(\"test\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'controller.get(\"test\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'model.get(\"test\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'this.foo.get(\"test\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'this.getWithDefault(\"test\", \"default\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'controller.getWithDefault(\"test\", \"default\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'this.set(\"test\", \"value\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'controller.set(\"test\", \"value\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'model.set(\"test\", \"value\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'this.getProperties(\"test\", \"test2\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'controller.getProperties(\"test\", \"test2\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'model.getProperties(\"test\", \"test2\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'this.setProperties({test: \"value\"})',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'controller.setProperties({test: \"value\"})',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'model.setProperties({test: \"value\"})',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'controller.getProperties(\"test\", \"test2\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'controller.setProperties({test: \"value\"})',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'controller.getWithDefault(\"test\", \"default\")',\n      output: null,\n      errors: [{ message: 'Use get/set' }],\n    },\n    // Fixable errors using local modules\n    {\n      code: 'import Ember from \"ember\"; const { get } = Ember; this.get(\"test\")',\n      output: 'import Ember from \"ember\"; const { get } = Ember; get(this, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { get } = Ember; controller.get(\"test\")',\n      output: 'import Ember from \"ember\"; const { get } = Ember; get(controller, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { get } = Ember; model.get(\"test\")',\n      output: 'import Ember from \"ember\"; const { get } = Ember; get(model, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { get } = Ember; this.foo.get(\"test\")',\n      output: 'import Ember from \"ember\"; const { get } = Ember; get(this.foo, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { getWithDefault } = Ember; this.getWithDefault(\"test\", \"default\")',\n      output:\n        'import Ember from \"ember\"; const { getWithDefault } = Ember; getWithDefault(this, \"test\", \"default\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { getWithDefault } = Ember; controller.getWithDefault(\"test\", \"default\")',\n      output:\n        'import Ember from \"ember\"; const { getWithDefault } = Ember; getWithDefault(controller, \"test\", \"default\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { set } = Ember; this.set(\"test\", \"value\")',\n      output: 'import Ember from \"ember\"; const { set } = Ember; set(this, \"test\", \"value\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { set } = Ember; controller.set(\"test\", \"value\")',\n      output: 'import Ember from \"ember\"; const { set } = Ember; set(controller, \"test\", \"value\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { set } = Ember; model.set(\"test\", \"value\")',\n      output: 'import Ember from \"ember\"; const { set } = Ember; set(model, \"test\", \"value\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { getProperties } = Ember; this.getProperties(\"test\", \"test2\")',\n      output:\n        'import Ember from \"ember\"; const { getProperties } = Ember; getProperties(this, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { getProperties } = Ember; controller.getProperties(\"test\", \"test2\")',\n      output:\n        'import Ember from \"ember\"; const { getProperties } = Ember; getProperties(controller, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { getProperties } = Ember; model.getProperties(\"test\", \"test2\")',\n      output:\n        'import Ember from \"ember\"; const { getProperties } = Ember; getProperties(model, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { setProperties } = Ember; this.setProperties({test: \"value\"})',\n      output:\n        'import Ember from \"ember\"; const { setProperties } = Ember; setProperties(this, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { setProperties } = Ember; controller.setProperties({test: \"value\"})',\n      output:\n        'import Ember from \"ember\"; const { setProperties } = Ember; setProperties(controller, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; const { setProperties } = Ember; model.setProperties({test: \"value\"})',\n      output:\n        'import Ember from \"ember\"; const { setProperties } = Ember; setProperties(model, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'import Ember from \"ember\"; const { getProperties } = Ember; controller.getProperties(\"test\", \"test2\")',\n      output:\n        'import Ember from \"ember\"; const { getProperties } = Ember; getProperties(controller, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'import Ember from \"ember\"; const { setProperties } = Ember; controller.setProperties({test: \"value\"})',\n      output:\n        'import Ember from \"ember\"; const { setProperties } = Ember; setProperties(controller, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'import Ember from \"ember\"; const { getWithDefault } = Ember; controller.getWithDefault(\"test\", \"default\")',\n      output:\n        'import Ember from \"ember\"; const { getWithDefault } = Ember; getWithDefault(controller, \"test\", \"default\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    // Fixable errors using method on Ember\n    {\n      code: 'import Ember from \"ember\"; this.get(\"test\")',\n      output: 'import Ember from \"ember\"; Ember.get(this, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; controller.get(\"test\")',\n      output: 'import Ember from \"ember\"; Ember.get(controller, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; model.get(\"test\")',\n      output: 'import Ember from \"ember\"; Ember.get(model, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; this.foo.get(\"test\")',\n      output: 'import Ember from \"ember\"; Ember.get(this.foo, \"test\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; this.getWithDefault(\"test\", \"default\")',\n      output: 'import Ember from \"ember\"; Ember.getWithDefault(this, \"test\", \"default\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; controller.getWithDefault(\"test\", \"default\")',\n      output: 'import Ember from \"ember\"; Ember.getWithDefault(controller, \"test\", \"default\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; this.set(\"test\", \"value\")',\n      output: 'import Ember from \"ember\"; Ember.set(this, \"test\", \"value\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; controller.set(\"test\", \"value\")',\n      output: 'import Ember from \"ember\"; Ember.set(controller, \"test\", \"value\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; model.set(\"test\", \"value\")',\n      output: 'import Ember from \"ember\"; Ember.set(model, \"test\", \"value\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; this.getProperties(\"test\", \"test2\")',\n      output: 'import Ember from \"ember\"; Ember.getProperties(this, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; controller.getProperties(\"test\", \"test2\")',\n      output: 'import Ember from \"ember\"; Ember.getProperties(controller, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; model.getProperties(\"test\", \"test2\")',\n      output: 'import Ember from \"ember\"; Ember.getProperties(model, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; this.setProperties({test: \"value\"})',\n      output: 'import Ember from \"ember\"; Ember.setProperties(this, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; controller.setProperties({test: \"value\"})',\n      output: 'import Ember from \"ember\"; Ember.setProperties(controller, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: 'import Ember from \"ember\"; model.setProperties({test: \"value\"})',\n      output: 'import Ember from \"ember\"; Ember.setProperties(model, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'import Ember from \"ember\"; controller.getProperties(\"test\", \"test2\")',\n      output: 'import Ember from \"ember\"; Ember.getProperties(controller, \"test\", \"test2\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'import Ember from \"ember\"; controller.setProperties({test: \"value\"})',\n      output: 'import Ember from \"ember\"; Ember.setProperties(controller, {test: \"value\"})',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      filename: 'app/tests/unit/controllers/controller-test.js',\n      code: 'import Ember from \"ember\"; controller.getWithDefault(\"test\", \"default\")',\n      output: 'import Ember from \"ember\"; Ember.getWithDefault(controller, \"test\", \"default\")',\n      parserOptions: { ecmaVersion: 2022, sourceType: 'module' },\n      errors: [{ message: 'Use get/set' }],\n    },\n\n    // ignoreNonThisExpressions\n    {\n      code: \"this.set('test', 'value')\",\n      output: null,\n      options: [{ ignoreNonThisExpressions: true }],\n      errors: [{ message: 'Use get/set' }],\n    },\n    {\n      code: \"this.get('value')\",\n      output: null,\n      options: [{ ignoreNonThisExpressions: true }],\n      errors: [{ message: 'Use get/set' }],\n    },\n\n    // ignoreThisExpressions\n    {\n      code: \"someObject.get('value')\",\n      output: null,\n      options: [{ ignoreThisExpressions: true }],\n      errors: [{ message: 'Use get/set' }],\n    },\n  ],\n});\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/ember_ts/bar.gts",
    "content": "export const fortyTwoFromGTS = '42';\n\n<template>\n  {{fortyTwoFromGTS}}\n</template>\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/ember_ts/baz.ts",
    "content": "export const fortyTwoFromTS = '42';\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/ember_ts/foo.gts",
    "content": "import { fortyTwoFromGTS } from './bar.gts';\nimport { fortyTwoFromTS } from './baz.ts';\n\nexport const fortyTwoLocal = '42';\n\nconst helloWorldFromTS = fortyTwoFromTS[0] === '4' ? 'hello' : 'world';\nconst helloWorldFromGTS = fortyTwoFromGTS[0] === '4' ? 'hello' : 'world';\nconst helloWorld = fortyTwoLocal[0] === '4' ? 'hello' : 'world';\n//\n<template>\n  {{helloWorldFromGTS}}\n  {{helloWorldFromTS}}\n  {{helloWorld}}\n</template>\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/gjs-gts-parser-test.js",
    "content": "'use strict';\n\n/**\n * Because this test needs the preprocessor, we can't use the normal\n * RuleTester api doesn't support preprocessors.\n *\n * @typedef {import('eslint/lib/cli-engine/cli-engine').CLIEngineOptions} CLIEngineOptions\n */\n\nconst { ESLint } = require('eslint');\nconst plugin = require('../../../lib');\nconst { writeFileSync, readFileSync } = require('node:fs');\nconst { join } = require('node:path');\n\nconst gjsGtsParser = require.resolve('ember-eslint-parser');\n\n/**\n * Helper function which creates ESLint instance with enabled/disabled autofix feature.\n *\n * @param {String} parser The parser to use.\n * @returns {ESLint} ESLint instance to execute in tests.\n */\nfunction initESLint(parser = gjsGtsParser) {\n  // tests must be run with ESLint 7+\n  return new ESLint({\n    ignore: false,\n    useEslintrc: false,\n    plugins: { ember: plugin },\n    overrideConfig: {\n      root: true,\n      env: {\n        browser: true,\n      },\n      parserOptions: {\n        ecmaVersion: 2022,\n        sourceType: 'module',\n      },\n      parser,\n      plugins: ['ember'],\n      extends: ['plugin:ember/recommended'],\n      overrides: [\n        {\n          files: ['**/*.gts'],\n          parserOptions: {\n            project: './tsconfig.eslint.json',\n            tsconfigRootDir: __dirname,\n            extraFileExtensions: ['.gts'],\n          },\n          extends: [\n            'plugin:@typescript-eslint/recommended-requiring-type-checking',\n            'plugin:ember/recommended',\n          ],\n          rules: {\n            'no-trailing-spaces': 'error',\n          },\n        },\n      ],\n      rules: {\n        quotes: ['error', 'single'],\n        semi: ['error', 'always'],\n        'object-curly-spacing': ['error', 'always'],\n        'lines-between-class-members': 'error',\n        'no-undef': 'error',\n        'no-unused-vars': 'error',\n        'ember/no-get': 'off',\n        'ember/no-array-prototype-extensions': 'error',\n        'ember/no-unused-services': 'error',\n      },\n    },\n  });\n}\n\nconst valid = [\n  {\n    filename: 'my-component.js',\n    code: `\n      import Component from '@glimmer/component';\n\n      export default class MyComponent extends Component {\n        constructor() {\n          super(...arguments);\n        }\n      }\n  `,\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import { on } from '@ember/modifier';\n\n      const noop = () => {};\n\n      export default <template>\n        <div {{on 'click' noop}} />\n      </template>\n    `,\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import Component from '@glimmer/component';\n      export default class MyComponent extends Component {\n        foo() {\n          return this.args.bar + this.args.baz;\n        }\n\n        <template>Hello World!</template>\n      }\n    `,\n  },\n  {\n    filename: 'my-component.gts',\n    code: `import Component from '@glimmer/component';\n\n    interface ListSignature<T> {\n      Args: {\n        items: Array<T>;\n      };\n      Blocks: {\n        default: [item: T]\n      };\n    }\n\n    export default class List<T> extends Component<ListSignature<T>> {\n      <template>\n        <div>Hello!</div>\n      </template>\n    }`,\n    parser: gjsGtsParser,\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import Component from '@glimmer/component';\n      import { inject as service } from '@ember/service';\n\n      export default class MyComponent extends Component {\n        @service foo;\n\n        <template>\n          {{this.foo}}\n          <div></div>\n          foobar\n        </template>\n      }\n    `,\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      const Foo = <template>hi</template>;\n\n      <template>\n        <Foo />\n      </template>\n    `,\n  },\n];\n\nconst invalid = [\n  {\n    parser: '@typescript-eslint/parser',\n    filename: 'my-component.gjs',\n    code: `import Component from '@glimmer/component';\n    export default class MyComponent extends Component {\n      <template>Hello!</template>\n    }`,\n    errors: [\n      {\n        message:\n          'Parsing error: Unexpected token. A constructor, method, accessor, or property was expected.\\n' +\n          'To lint Gjs/Gts files please follow the setup guide at https://github.com/ember-cli/eslint-plugin-ember#gtsgjs\\n' +\n          'Note that this error can also happen if you have multiple versions of eslint-plugin-ember in your node_modules',\n        line: 3,\n        column: 6,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `import Component from '@glimmer/component';\n    export default classsss MyComponent extends Component {\n      <template>Hello!</template>\n    }`,\n    errors: [\n      {\n        message: `Parsing error: × Expected ';', got 'MyComponent'\n   ╭─[1:1]\n 1 │ import Component from '@glimmer/component';\n 2 │     export default classsss MyComponent extends Component {\n   ·                             ───────────\n 3 │       <template>Hello!</template>\n   ╰────`,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import { on } from '@ember/modifier';\n\n      const noop = () => {};\n\n      <template>\n        <div {{on 'click' noop}} />\n      </template>\n\n      <template>\n        <div {{on 'click' noop}} />\n      </template>\n    `,\n    errors: [\n      {\n        message: 'Missing semicolon.',\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `import Component from '@glimmer/component';\n    export default class MyComponent extends Component {\n      <template>Hello!</template>\n    }`,\n    errors: [\n      {\n        message: 'Do not create empty backing classes for Glimmer template tag only components.',\n        line: 2,\n        column: 20,\n        endColumn: 6,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      const noop = () => {};\n\n      <template>\n        {{on 'click' noop}}\n      </template>\n    `,\n    errors: [\n      {\n        message: \"'on' is not defined.\",\n        line: 5,\n        column: 11,\n        endColumn: 13,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      <template>\n        {{noop}}\n      </template>\n    `,\n    errors: [\n      {\n        message: \"'noop' is not defined.\",\n        line: 3,\n        column: 11,\n        endColumn: 15,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      <template>\n      {{#let 'x' as |noop usedEl notUsed|}}\n        {{noop}}\n        <usedEl />\n        <undef.x />\n      {{/let}}\n      </template>\n    `,\n    errors: [\n      {\n        column: 34,\n        endColumn: 41,\n        endLine: 3,\n        line: 3,\n        message: \"'notUsed' is defined but never used.\",\n        messageId: 'unusedVar',\n        nodeType: 'GlimmerBlockParam',\n        ruleId: 'no-unused-vars',\n        severity: 2,\n      },\n      {\n        column: 10,\n        endColumn: 15,\n        endLine: 6,\n        line: 6,\n        message: \"'undef' is not defined.\",\n        messageId: 'undef',\n        nodeType: 'GlimmerElementNodePart',\n        ruleId: 'no-undef',\n        severity: 2,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      <template>\n        <Foo />\n        <Bar>\n          <div></div>\n        </Bar>\n      </template>\n    `,\n    errors: [\n      {\n        message: \"'Foo' is not defined.\",\n        line: 3,\n        endLine: 3,\n        column: 10,\n        endColumn: 13,\n      },\n      {\n        message: \"'Bar' is not defined.\",\n        line: 4,\n        endLine: 4,\n        column: 10,\n        endColumn: 13,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      <template>\n        <F_0_O />\n      </template>\n    `,\n    errors: [\n      {\n        message: \"'F_0_O' is not defined.\",\n        line: 3,\n        column: 10,\n        endColumn: 15,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import Component from '@glimmer/component';\n\n      export default class MyComponent extends Component {\n        foo = 'bar';\n        <template>\"hi\"</template>\n      }\n    `,\n    errors: [\n      {\n        message: 'Expected blank line between class members.',\n        line: 6,\n        endLine: 6,\n        column: 9,\n        endColumn: 34,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import Component from '@glimmer/component';\n\n      export default class MyComponent extends Component {\n        foo = 'bar';\n        <template>\"hi\"\n        </template>\n      }\n    `,\n    errors: [\n      {\n        message: 'Expected blank line between class members.',\n        line: 6,\n        endLine: 7,\n        column: 9,\n        endColumn: 20,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gts',\n    parser: gjsGtsParser,\n    code: `\n      import Component from '@glimmer/component';\n\n      const foo: any = '';\n\n      export default class MyComponent extends Component {\n        foo = 'bar';\n\n        <template>\n          <div></div>${'  '}\n          {{foo}}\n        </template>\n      }`,\n    errors: [\n      {\n        message: 'Unexpected any. Specify a different type.',\n        line: 4,\n        endLine: 4,\n        column: 18,\n        endColumn: 21,\n      },\n      {\n        message: 'Trailing spaces not allowed.',\n        line: 10,\n        endLine: 10,\n        column: 22,\n        endColumn: 24,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import Component from \"@glimmer/component\";\n      export default class MyComponent extends Component {\n        foo = 'bar';\n        <template>\"hi\"\n        </template>\n      }\n    `,\n    errors: [\n      {\n        message: 'Strings must use singlequote.',\n        line: 2,\n        endLine: 2,\n        endColumn: 49,\n        column: 29,\n        fix: {\n          range: [29, 49],\n        },\n      },\n      {\n        message: 'Expected blank line between class members.',\n        line: 5,\n        endLine: 6,\n        column: 9,\n        endColumn: 20,\n      },\n    ],\n  },\n  {\n    filename: 'my-component.gjs',\n    code: `\n      import Component from '@glimmer/component';\n      import { inject as service } from '@ember/service';\n\n      export default class MyComponent extends Component {\n        @service foo;\n\n        @service bar;\n\n        <template>\n          {{this.foo.bar}}\n          {{this.bartender}}\n          <div>this.bar</div>\n          this.bar.foo\n          something.bar\n        </template>\n      }\n    `,\n    errors: [\n      {\n        message:\n          'The service `bar` is not referenced in this file and might be unused (note: it could still be used in a corresponding handlebars template file, mixin, or parent/child class).',\n        line: 8,\n        endLine: 8,\n        endColumn: 22,\n        column: 9,\n      },\n    ],\n  },\n];\n\ndescribe('template-vars', () => {\n  describe('valid', () => {\n    for (const scenario of valid) {\n      const { code, filename, parser } = scenario;\n\n      it(code, async () => {\n        const eslint = initESLint(parser);\n        const results = await eslint.lintText(code, {\n          filePath: `./tests/lib/rules-preprocessor/${filename}`,\n        });\n        const resultErrors = results.flatMap((result) => result.messages);\n\n        // This gives more meaningful information than\n        // checking if results is empty / length === 0\n        expect(results[0]?.messages).toHaveLength(0);\n        expect(resultErrors).toHaveLength(0);\n      });\n    }\n  });\n\n  describe('invalid', () => {\n    for (const scenario of invalid) {\n      const { code, filename, errors, parser } = scenario;\n\n      it(code, async () => {\n        const eslint = initESLint(parser);\n        const results = await eslint.lintText(code, {\n          filePath: `./tests/lib/rules-preprocessor/${filename}`,\n        });\n\n        const resultErrors = results.flatMap((result) => result.messages);\n        expect(resultErrors).toHaveLength(errors.length);\n\n        for (const [index, error] of resultErrors.entries()) {\n          const expected = errors[index];\n\n          for (const key of Object.keys(expected)) {\n            // Prefix with what key we are looking at so\n            // that debugging is less painful\n            const expectedString = `${key}: ${expected[key]}`;\n            const actualString = `${key}: ${error[key]}`;\n\n            expect(actualString).toStrictEqual(expectedString);\n          }\n        }\n      });\n    }\n  });\n});\n\ndescribe('line/col numbers should be correct', () => {\n  it('line and col should be correct', async () => {\n    const eslint = initESLint();\n    const code = `\n    import Component from '@glimmer/component';\n    import { get } from '@ember/object';\n\n    export default class MyComponent extends Component {\n      constructor() {\n        super(...arguments);\n      }\n\n      get truncatedList() {\n        return get(\n          this.truncatedList,\n          this.isImgList ? 'firstObject.attributes.firstObject' : 'firstObject'\n        );\n      }\n\n      <template>\n        <div>this is necessary to break the tests</div>\n      </template>\n    }\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(2);\n\n    expect(resultErrors[0]).toStrictEqual({\n      column: 28,\n      endColumn: 64,\n      endLine: 13,\n      line: 13,\n      message: \"Don't use Ember's array prototype extensions\",\n      messageId: 'main',\n      nodeType: 'Literal',\n      ruleId: 'ember/no-array-prototype-extensions',\n      severity: 2,\n    });\n\n    expect(resultErrors[1]).toStrictEqual({\n      column: 67,\n      endColumn: 80,\n      endLine: 13,\n      line: 13,\n      message: \"Don't use Ember's array prototype extensions\",\n      messageId: 'main',\n      nodeType: 'Literal',\n      ruleId: 'ember/no-array-prototype-extensions',\n      severity: 2,\n    });\n  });\n});\n\ndescribe('lint errors on the exact line as the <template> tag', () => {\n  it('correctly outputs the lint error', async () => {\n    const eslint = initESLint();\n    const code = `\n    import Component from '@glimmer/component';\n\n    export default class MyComponent extends Component {\n      constructor() {\n        super(...arguments);\n      }\n\n      foo = 'bar';\n      <template>\n        <div>\n          some totally random, non-meaningful text\n        </div>\n      </template>\n    }\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(1);\n    expect(resultErrors[0].message).toBe('Expected blank line between class members.');\n  });\n});\n\ndescribe('supports eslint directives inside templates', () => {\n  it('works with mustache comment', async () => {\n    const eslint = initESLint();\n    const code = `\n    // test comment\n    <template>\n      <div>\n        {{!eslint-disable-next-line}}\n        {{test}}\n      </div>\n      <div>\n        {{!--eslint-disable--}}\n        {{test}}\n        {{test}}\n        {{test}}\n        {{!--eslint-enable--}}\n      </div>\n    </template>\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(0);\n  });\n  it('works with html comment', async () => {\n    const eslint = initESLint();\n    const code = `\n    <template>\n      <div>\n        <!--eslint-disable-next-line-->\n        {{test}}\n      </div>\n      <div>\n        <!-- eslint-disable -->\n        {{test}}\n        {{test}}\n        {{test}}\n        <!-- eslint-enable -->\n      </div>\n    </template>\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(0);\n  });\n});\n\ndescribe('multiple tokens in same file', () => {\n  it('correctly maps duplicate tokens to the correct lines', async () => {\n    const eslint = initESLint();\n    const code = `\n      // comment one\n      // comment two\n      // comment three\n      const two = 2;\n\n      const three = <template> \"bar\" </template>;\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(2);\n\n    expect(resultErrors[0]).toStrictEqual({\n      column: 13,\n      endColumn: 16,\n      endLine: 5,\n      line: 5,\n      message: \"'two' is assigned a value but never used.\",\n      messageId: 'unusedVar',\n      nodeType: 'Identifier',\n      ruleId: 'no-unused-vars',\n      severity: 2,\n    });\n\n    expect(resultErrors[1]).toStrictEqual({\n      column: 13,\n      endColumn: 18,\n      endLine: 7,\n      line: 7,\n      message: \"'three' is assigned a value but never used.\",\n      messageId: 'unusedVar',\n      nodeType: 'Identifier',\n      ruleId: 'no-unused-vars',\n      severity: 2,\n    });\n  });\n\n  it('handles duplicate template tokens', async () => {\n    const eslint = initESLint();\n    const code = `\n      // comment Bad\n\n      const tmpl = <template><Bad /></template>;\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(2);\n\n    expect(resultErrors[0]).toStrictEqual({\n      column: 13,\n      endColumn: 17,\n      endLine: 4,\n      line: 4,\n      message: \"'tmpl' is assigned a value but never used.\",\n      messageId: 'unusedVar',\n      nodeType: 'Identifier',\n      ruleId: 'no-unused-vars',\n      severity: 2,\n    });\n\n    expect(resultErrors[1]).toStrictEqual({\n      column: 31,\n      endColumn: 34,\n      endLine: 4,\n      line: 4,\n      message: \"'Bad' is not defined.\",\n      messageId: 'undef',\n      nodeType: 'GlimmerElementNodePart',\n      ruleId: 'no-undef',\n      severity: 2,\n    });\n  });\n\n  it('correctly maps tokens after handlebars', async () => {\n    const eslint = initESLint();\n    const code = `\n    import Component from '@glimmer/component';\n\n    export const fakeTemplate = <template>\n      <div>{{foo}}</div>\n    </template>;\n\n    export default class MyComponent extends Component {\n      constructor() {\n        super(...arguments);\n      }\n\n      foo = bar;\n\n      <template>\n        <div>\n          some totally random, non-meaningful text {{bar}}\n        </div>\n      </template>\n    }\n    `;\n    const results = await eslint.lintText(code, { filePath: 'my-component.gjs' });\n\n    const resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(3);\n    expect(resultErrors[0].message).toBe(\"'foo' is not defined.\");\n    expect(resultErrors[0].line).toBe(5);\n\n    expect(resultErrors[1].message).toBe(\"'bar' is not defined.\");\n    expect(resultErrors[1].endLine).toBe(13);\n    expect(resultErrors[1].line).toBe(13);\n\n    expect(resultErrors[2].message).toBe(\"'bar' is not defined.\");\n    expect(resultErrors[2].line).toBe(17);\n  });\n\n  it('lints while being type aware', async () => {\n    const eslint = new ESLint({\n      cwd: __dirname,\n      ignore: false,\n      useEslintrc: false,\n      plugins: { ember: plugin },\n      overrideConfig: {\n        root: true,\n        env: {\n          browser: true,\n        },\n        plugins: ['ember'],\n        extends: ['plugin:ember/recommended'],\n        overrides: [\n          {\n            files: ['**/*.gts'],\n            parser: 'ember-eslint-parser',\n            parserOptions: {\n              project: './tsconfig.eslint.json',\n              tsconfigRootDir: __dirname,\n              extraFileExtensions: ['.gts'],\n            },\n            extends: [\n              'plugin:@typescript-eslint/recommended-requiring-type-checking',\n              'plugin:ember/recommended',\n            ],\n            rules: {\n              'no-trailing-spaces': 'error',\n              '@typescript-eslint/prefer-string-starts-ends-with': 'error',\n            },\n          },\n          {\n            files: ['**/*.ts'],\n            parser: '@typescript-eslint/parser',\n            parserOptions: {\n              project: './tsconfig.eslint.json',\n              tsconfigRootDir: __dirname,\n              extraFileExtensions: ['.gts'],\n            },\n            extends: [\n              'plugin:@typescript-eslint/recommended-requiring-type-checking',\n              'plugin:ember/recommended',\n            ],\n            rules: {\n              'no-trailing-spaces': 'error',\n            },\n          },\n        ],\n        rules: {\n          quotes: ['error', 'single'],\n          semi: ['error', 'always'],\n          'object-curly-spacing': ['error', 'always'],\n          'lines-between-class-members': 'error',\n          'no-undef': 'error',\n          'no-unused-vars': 'error',\n          'ember/no-get': 'off',\n          'ember/no-array-prototype-extensions': 'error',\n          'ember/no-unused-services': 'error',\n        },\n      },\n    });\n\n    let results = await eslint.lintFiles(['**/*.gts', '**/*.ts']);\n\n    let resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(3);\n\n    expect(resultErrors[0].message).toBe(\"Use 'String#startsWith' method instead.\");\n    expect(resultErrors[0].line).toBe(6);\n\n    expect(resultErrors[1].line).toBe(7);\n    expect(resultErrors[1].message).toBe(\"Use 'String#startsWith' method instead.\");\n\n    expect(resultErrors[2].line).toBe(8);\n    expect(resultErrors[2].message).toBe(\"Use 'String#startsWith' method instead.\");\n\n    const filePath = join(__dirname, 'ember_ts', 'bar.gts');\n    const content = readFileSync(filePath).toString();\n    try {\n      writeFileSync(filePath, content.replace(\"'42'\", '42'));\n\n      results = await eslint.lintFiles(['**/*.gts', '**/*.ts']);\n\n      resultErrors = results.flatMap((result) => result.messages);\n      expect(resultErrors).toHaveLength(2);\n\n      expect(resultErrors[0].message).toBe(\"Use 'String#startsWith' method instead.\");\n      expect(resultErrors[0].line).toBe(6);\n\n      expect(resultErrors[1].line).toBe(8);\n      expect(resultErrors[1].message).toBe(\"Use 'String#startsWith' method instead.\");\n    } finally {\n      writeFileSync(filePath, content);\n    }\n\n    results = await eslint.lintFiles(['**/*.gts', '**/*.ts']);\n\n    resultErrors = results.flatMap((result) => result.messages);\n    expect(resultErrors).toHaveLength(3);\n\n    expect(resultErrors[0].message).toBe(\"Use 'String#startsWith' method instead.\");\n    expect(resultErrors[0].line).toBe(6);\n\n    expect(resultErrors[1].message).toBe(\"Use 'String#startsWith' method instead.\");\n    expect(resultErrors[1].line).toBe(7);\n\n    expect(resultErrors[2].line).toBe(8);\n    expect(resultErrors[2].message).toBe(\"Use 'String#startsWith' method instead.\");\n  });\n});\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/my-component.gts",
    "content": "// needed for typed linting\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/template-no-deprecated/component-stub.ts",
    "content": "export default class ComponentBase<S extends object = object> {\n  declare args: S;\n}\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/template-no-deprecated/component-with-args.ts",
    "content": "import ComponentBase from './component-stub';\n\nexport default class ComponentWithArgs extends ComponentBase<{\n  Args: {\n    /** @deprecated use newArg instead */\n    oldArg: string;\n    /** @deprecated */\n    oldArgNoReason: string;\n    newArg: string;\n  };\n}> {}\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/template-no-deprecated/current-component.ts",
    "content": "export default class CurrentComponent {}\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/template-no-deprecated/deprecated-component.ts",
    "content": "/** @deprecated use NewComponent instead */\nexport default class DeprecatedComponent {}\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/template-no-deprecated/deprecated-helper.ts",
    "content": "/** @deprecated */\nexport function deprecatedHelper(): string {\n  return 'deprecated';\n}\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/template-no-deprecated/usage.gts",
    "content": "// Placeholder file — actual code is provided inline by tests.\n// Its presence lets TypeScript include this path in the program.\n"
  },
  {
    "path": "tests/lib/rules-preprocessor/tsconfig.eslint.json",
    "content": "{\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n    \"allowJs\": true,\n    \"strictNullChecks\": true\n  },\n  \"include\": [\"**/*.ts\", \"**/*.gts\"]\n}\n"
  },
  {
    "path": "tests/lib/utils/computed-properties-test.js",
    "content": "const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst computedPropertyUtils = require('../../../lib/utils/computed-properties');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0].expression;\n}\n\ndescribe('getComputedPropertyFunctionBody', () => {\n  it('gets the result with no dependent keys and no function', () => {\n    const node = parse('computed()');\n    expect(computedPropertyUtils.getComputedPropertyFunctionBody(node)).toBeUndefined();\n  });\n\n  it('gets the result with one dependent key and no function', () => {\n    const node = parse('computed(\"dep\")');\n    expect(computedPropertyUtils.getComputedPropertyFunctionBody(node)).toBeUndefined();\n  });\n\n  it('gets the result when using FunctionExpression and no dependent keys', () => {\n    const node = parse('computed(function() {})');\n    expect(computedPropertyUtils.getComputedPropertyFunctionBody(node).type).toBe('BlockStatement');\n  });\n\n  it('gets the result when using FunctionExpression and one dependent key', () => {\n    const node = parse('computed(\"dep1\", function() {})');\n    expect(computedPropertyUtils.getComputedPropertyFunctionBody(node).type).toBe('BlockStatement');\n  });\n\n  it('gets the result when using explicit getter', () => {\n    const node = parse('computed(\"dep1\", { get() {}})');\n    expect(computedPropertyUtils.getComputedPropertyFunctionBody(node).type).toBe('BlockStatement');\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/computed-property-dependent-keys-test.js",
    "content": "'use strict';\n\nconst cpdkUtils = require('../../../lib/utils/computed-property-dependent-keys');\nconst { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\n\ndescribe('collapseKeys', () => {\n  it('returns the right result', () => {\n    // No opportunity for braces\n    expect(cpdkUtils.collapseKeys([])).toStrictEqual([]);\n    expect(cpdkUtils.collapseKeys(['foo'])).toStrictEqual([\"'foo'\"]);\n    expect(cpdkUtils.collapseKeys(['foo', 'bar'])).toStrictEqual([\"'bar'\", \"'foo'\"]);\n    expect(cpdkUtils.collapseKeys(['foo.abc', 'bar.def'])).toStrictEqual([\n      \"'bar.def'\",\n      \"'foo.abc'\",\n    ]);\n\n    // Opportunity for braces\n    expect(cpdkUtils.collapseKeys(['foo.abc', 'foo.def'])).toStrictEqual([\"'foo.{abc,def}'\"]);\n  });\n});\n\ndescribe('expandKey', () => {\n  it('returns the right result', () => {\n    // No expansion possible\n    expect(cpdkUtils.expandKey('')).toBe('');\n    expect(cpdkUtils.expandKey('foo')).toBe('foo');\n    expect(cpdkUtils.expandKey('foo.bar')).toBe('foo.bar');\n    expect(cpdkUtils.expandKey('foo.{bar}')).toStrictEqual(['foo.bar']);\n\n    // Expansion possible\n    expect(cpdkUtils.expandKey('foo.{bar1,bar2}')).toStrictEqual(['foo.bar1', 'foo.bar2']);\n    expect(cpdkUtils.expandKey('foo.@each.{bar1,bar2}')).toStrictEqual([\n      'foo.@each.bar1',\n      'foo.@each.bar2',\n    ]);\n    expect(cpdkUtils.expandKey('{foo,bar}')).toStrictEqual(['foo', 'bar']);\n  });\n});\n\ndescribe('computedPropertyDependencyMatchesKeyPath', () => {\n  it('returns the right result', () => {\n    // False:\n    expect(cpdkUtils.computedPropertyDependencyMatchesKeyPath('foo', 'bar')).toBe(false);\n    expect(cpdkUtils.computedPropertyDependencyMatchesKeyPath('foo.bar', 'bar')).toBe(false);\n\n    // True:\n    expect(cpdkUtils.computedPropertyDependencyMatchesKeyPath('foo.bar', 'foo')).toBe(true);\n    expect(cpdkUtils.computedPropertyDependencyMatchesKeyPath('foo.@each.bar', 'foo')).toBe(true);\n    expect(cpdkUtils.computedPropertyDependencyMatchesKeyPath('foo.[]', 'foo')).toBe(true);\n    expect(cpdkUtils.computedPropertyDependencyMatchesKeyPath('foo.bar.xyz', 'foo.bar')).toBe(true);\n  });\n});\n\ndescribe('keyExistsAsPrefixInList', () => {\n  it('returns the right result', () => {\n    // False:\n    expect(cpdkUtils.keyExistsAsPrefixInList(['a', 'b.c'], 'x')).toBe(false);\n    expect(cpdkUtils.keyExistsAsPrefixInList(['a', 'b.c'], 'c')).toBe(false);\n\n    // True:\n    expect(cpdkUtils.keyExistsAsPrefixInList(['a', 'b.c'], 'b')).toBe(true);\n  });\n});\n\ndescribe('findComputedPropertyDependentKeys', () => {\n  it('returns the right result with native class', () => {\n    const computedImportName = 'c';\n    const macrosByImport = new Map([\n      [\n        'rejectBy',\n        {\n          argumentFormat: [\n            {\n              strings: {\n                startIndex: 0,\n                count: 1,\n              },\n            },\n          ],\n        },\n      ],\n      [\n        't',\n        {\n          argumentFormat: [\n            {\n              objects: { index: 1, keys: ['foo', 'bar', 'baz'] },\n            },\n          ],\n        },\n      ],\n    ]);\n\n    const macrosByIndexImport = new Map([\n      [\n        'c',\n        new Map([\n          [\n            'readOnly',\n            {\n              argumentFormat: [\n                {\n                  strings: {\n                    startIndex: 0,\n                    count: 1,\n                  },\n                },\n              ],\n            },\n          ],\n        ]),\n      ],\n      [\n        'somethingElse',\n        new Map([\n          [\n            'strange',\n            {\n              argumentFormat: [\n                {\n                  strings: {\n                    startIndex: 1,\n                    count: 2,\n                  },\n                },\n                {\n                  objects: {\n                    index: 3,\n                  },\n                },\n              ],\n            },\n          ],\n        ]),\n      ],\n    ]);\n\n    const classNode = babelESLintParse(`\n      import { computed as c } from '@ember/object';\n      import { rejectBy, t } from 'custom-macros/macros';\n      import { somethingElse } from 'custom-macros';\n      import Component from '@ember/component';\n\n      class MyClass extends Component {\n        @c('x') get myProp() {}\n        @c('x', 'x') get myProp() {} // Intentional duplicate dependent key.\n        @c() get myProp() {}\n        @c.readOnly('y') get myProp() {}\n        @c.notSpecified('z') get myProp() {}\n        @rejectBy('chores', 'done', true) get myProp() {}\n        @random() get myProp() {}\n        @random('') get myProp() {}\n        @random('z') get myProp() {}\n        @computed('bad') get myProp() {} // Wrong name.\n        @readOnly('bad') get myProp() {} // Wrong name.\n        @t('ignored', { foo: 'kept', \"bar\": 'also-kept', unknown: 'not-kept' }) stringProp;\n        @somethingElse.strange('ignored', 'yes', 'also-yes', { arbitrary: 'indeed' }) otherProp;\n      }\n    `).body[4];\n    expect([\n      ...cpdkUtils.findComputedPropertyDependentKeys(\n        classNode,\n        computedImportName,\n        macrosByImport,\n        macrosByIndexImport\n      ),\n    ]).toStrictEqual(['x', 'y', 'chores', 'kept', 'also-kept', 'yes', 'also-yes', 'indeed']);\n  });\n\n  it('returns the right result with classic class', () => {\n    const computedImportName = 'c';\n    const macrosByImport = new Map([\n      [\n        'rejectBy',\n        {\n          argumentFormat: [\n            {\n              strings: {\n                startIndex: 0,\n                count: 1,\n              },\n            },\n          ],\n        },\n      ],\n      [\n        't',\n        {\n          argumentFormat: [\n            {\n              objects: { index: 1, keys: ['foo', 'bar', 'baz'] },\n            },\n          ],\n        },\n      ],\n    ]);\n\n    const macrosByIndexImport = new Map([\n      [\n        'c',\n        new Map([\n          [\n            'readOnly',\n            {\n              argumentFormat: [\n                {\n                  strings: {\n                    startIndex: 0,\n                    count: 1,\n                  },\n                },\n              ],\n            },\n          ],\n        ]),\n      ],\n      [\n        'somethingElse',\n        new Map([\n          [\n            'strange',\n            {\n              argumentFormat: [\n                {\n                  strings: {\n                    startIndex: 1,\n                    count: 2,\n                  },\n                },\n                {\n                  objects: {\n                    index: 3,\n                  },\n                },\n              ],\n            },\n          ],\n        ]),\n      ],\n    ]);\n\n    const classNode = babelESLintParse(`\n      import { computed as c } from '@ember/object';\n      import { rejectBy, t } from 'custom-macros/macros';\n      import { somethingElse } from 'custom-macros';\n      import Component from '@ember/component';\n\n      Component.extend({\n        prop1: c('x'),\n        prop2: c('x', 'x'), // Intentional duplicate dependent key.\n        prop3: c(),\n        prop5: c?.readOnly('y'), // Macro plus optional expression.\n        prop4: c.notSpecified('z'),\n        prop6: rejectBy('chores', 'done', true),\n        prop7: random(),\n        prop8: random(''),\n        prop9: random('z'),\n        prop10: computed('bad'), // Wrong name.\n        prop11: readOnly('bad'), // Wrong name.\n        prop12: t('ignored', { foo: 'kept', \"bar\": 'also-kept', unknown: 'not-kept' }),\n        prop13: somethingElse.strange('ignored', 'yes', 'also-yes', { arbitrary: 'indeed' }),\n      });\n    `).body[4].expression;\n    expect([\n      ...cpdkUtils.findComputedPropertyDependentKeys(\n        classNode,\n        computedImportName,\n        macrosByImport,\n        macrosByIndexImport\n      ),\n    ]).toStrictEqual(['x', 'y', 'chores', 'kept', 'also-kept', 'yes', 'also-yes', 'indeed']);\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/computed-property-macros-test.js",
    "content": "const {\n  getMacros,\n  getMacrosFromImports,\n  getTrackedArgumentCount,\n  macroToCanonicalName,\n} = require('../../../lib/utils/computed-property-macros');\n\ndescribe('getMacros', () => {\n  it('returns some of the correct macros', () => {\n    expect(getMacros()).toStrictEqual(expect.arrayContaining(['and', 'readOnly']));\n  });\n});\n\ndescribe('getMacrosFromImports', () => {\n  it('returns some of the correct macros', () => {\n    const macrosByImport = new Map([\n      ['readOnlyRenamed', 'read-only-config'],\n      ['aliasRenamed', 'alias-config'],\n    ]);\n    const macrosByIndexImport = new Map([\n      [\n        'computed',\n        new Map([\n          ['readOnly', 'read-only-config'],\n          ['alias', 'alias-config'],\n        ]),\n      ],\n      ['customComputed', new Map([['rejectBy', 'reject-by-config']])],\n    ]);\n    const result = [...getMacrosFromImports(macrosByImport, macrosByIndexImport)];\n    expect(result).toStrictEqual(\n      expect.arrayContaining([\n        ['readOnlyRenamed', 'read-only-config'],\n        ['aliasRenamed', 'alias-config'],\n        ['computed.readOnly', 'read-only-config'],\n        ['computed.alias', 'alias-config'],\n        ['customComputed.rejectBy', 'reject-by-config'],\n      ])\n    );\n  });\n});\n\ndescribe('getTrackedArgumentCount', () => {\n  it('returns the correct number for some example macros', () => {\n    expect(getTrackedArgumentCount('and')).toStrictEqual(Number.MAX_VALUE);\n    expect(getTrackedArgumentCount('readOnly')).toBe(1);\n  });\n});\n\ndescribe('macroToCanonicalName', () => {\n  it('returns the correct canonical name for some example macros', () => {\n    const macroImportNames = new Map([\n      ['and', 'and'],\n      ['readOnly', 'readOnlyRenamed'],\n    ]);\n\n    expect(macroToCanonicalName('and', macroImportNames)).toBe('and');\n    expect(macroToCanonicalName('readOnlyRenamed', macroImportNames)).toBe('readOnly');\n\n    expect(macroToCanonicalName('computed.readOnly', macroImportNames)).toBe('readOnly');\n    expect(macroToCanonicalName('computedRenamed.readOnly', macroImportNames)).toBe('readOnly');\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/decorators-test.js",
    "content": "const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst decoratorUtils = require('../../../lib/utils/decorators');\n\ndescribe('getDecoratorName', () => {\n  it('should return decorator name with Identifier', () => {\n    const node = babelESLintParse('class Test { @computed get prop() {} }').body[0].body.body[0];\n    expect(decoratorUtils.getDecoratorName(node.decorators[0])).toBe('computed');\n  });\n\n  it('should return decorator name with CallExpression', () => {\n    const node = babelESLintParse('class Test { @computed() get prop() {} }').body[0].body.body[0];\n    expect(decoratorUtils.getDecoratorName(node.decorators[0])).toBe('computed');\n  });\n\n  it('should return decorator name with MemberExpression', () => {\n    const node = babelESLintParse('class Test { @computed.readOnly() get prop() {} }').body[0].body\n      .body[0];\n    expect(decoratorUtils.getDecoratorName(node.decorators[0])).toBe('computed.readOnly');\n  });\n});\n\ndescribe('findDecorator', () => {\n  it('should not find anything with non-decorator', () => {\n    const node = babelESLintParse('const x = 123').body[0];\n    expect(decoratorUtils.findDecorator(node, 'random')).toBeUndefined();\n  });\n\n  it('should not find anything with wrong decorator name', () => {\n    const node = babelESLintParse('class Test { @computed get prop() {} }').body[0].body.body[0];\n    expect(decoratorUtils.findDecorator(node, 'random')).toBeUndefined();\n  });\n\n  it('should find something with Identifier decorator', () => {\n    const node = babelESLintParse('class Test { @computed get prop() {} }').body[0].body.body[0];\n    expect(decoratorUtils.findDecorator(node, 'computed')).toBeTruthy();\n  });\n\n  it('should find something with CallExpression decorator', () => {\n    const node = babelESLintParse('class Test { @computed() get prop() {} }').body[0].body.body[0];\n    expect(decoratorUtils.findDecorator(node, 'computed')).toBeTruthy();\n  });\n\n  it('should find something with MemberExpression decorator', () => {\n    const node = babelESLintParse('class Test { @computed.readOnly() get prop() {} }').body[0].body\n      .body[0];\n    expect(decoratorUtils.findDecorator(node, 'computed.readOnly')).toBeTruthy();\n  });\n});\n\ndescribe('findDecoratorByNameCallback', () => {\n  it('should not find anything with callback checking for wrong name', () => {\n    const node = babelESLintParse('class Test { @computed get prop() {} }').body[0].body.body[0];\n    expect(\n      decoratorUtils.findDecoratorByNameCallback(node, (name) => name === 'random')\n    ).toBeUndefined();\n  });\n\n  it('should find decorator with callback checking for correct name', () => {\n    const node = babelESLintParse('class Test { @computed get prop() {} }').body[0].body.body[0];\n    expect(\n      decoratorUtils.findDecoratorByNameCallback(node, (name) => name === 'computed')\n    ).toStrictEqual(node.decorators[0]);\n  });\n});\n\nconst expressionlessParse = (code) => babelESLintParse(code).body[0];\ndescribe('hasDecorator', () => {\n  const withDecorator = '@classic class Rectangle {}';\n  const withoutDecorator = 'class Rectangle {}';\n  const testCases = [\n    {\n      code: withoutDecorator,\n      decoratorName: undefined,\n      expected: false,\n    },\n    {\n      code: withoutDecorator,\n      decoratorName: 'classic',\n      expected: false,\n    },\n    {\n      code: withDecorator,\n      decoratorName: undefined,\n      expected: false,\n    },\n    {\n      code: withDecorator,\n      decoratorName: 'classic',\n      expected: true,\n    },\n    {\n      code: withDecorator,\n      decoratorName: 'someOtherDecoratorName',\n      expected: false,\n    },\n  ];\n  for (const { code, decoratorName, expected } of testCases) {\n    it(`('${code}', '${decoratorName}') => ${expected}`, () => {\n      const node = expressionlessParse(code);\n      expect(decoratorUtils.hasDecorator(node, decoratorName)).toStrictEqual(expected);\n    });\n  }\n});\n\ndescribe('isClassPropertyOrPropertyDefinitionWithDecorator', () => {\n  it('should not find anything with non-decorator', () => {\n    const node = babelESLintParse('const x = 123').body[0];\n    expect(decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(node, 'random')).toBe(\n      false\n    );\n  });\n\n  it('should not find anything with wrong decorator name', () => {\n    const node = babelESLintParse('class Test { @tracked x }').body[0].body.body[0];\n    expect(decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(node, 'random')).toBe(\n      false\n    );\n  });\n\n  it('should find something with Identifier decorator', () => {\n    const node = babelESLintParse('class Test { @tracked x }').body[0].body.body[0];\n    expect(decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(node, 'tracked')).toBe(\n      true\n    );\n  });\n\n  it('should find something with CallExpression decorator', () => {\n    const node = babelESLintParse('class Test { @tracked x }').body[0].body.body[0];\n    expect(decoratorUtils.isClassPropertyOrPropertyDefinitionWithDecorator(node, 'tracked')).toBe(\n      true\n    );\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/editorconfig-test.js",
    "content": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst os = require('os');\nconst { resolveEditorConfig } = require('../../../lib/utils/editorconfig');\n\ndescribe('resolveEditorConfig', () => {\n  let tmpDir;\n\n  beforeEach(() => {\n    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'editorconfig-test-'));\n  });\n\n  afterEach(() => {\n    fs.rmSync(tmpDir, { recursive: true, force: true });\n  });\n\n  it('returns empty object when no .editorconfig exists', () => {\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result).toEqual({});\n  });\n\n  it('reads indent_size from .editorconfig', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*]', 'indent_size = 4'].join('\\n')\n    );\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_size).toBe(4);\n  });\n\n  it('matches *.hbs sections', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*.hbs]', 'indent_size = 3'].join('\\n')\n    );\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_size).toBe(3);\n  });\n\n  it('does not match non-matching glob', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*.js]', 'indent_size = 4'].join('\\n')\n    );\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_size).toBeUndefined();\n  });\n\n  it('handles brace expansion *.{hbs,gjs}', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*.{hbs,gjs}]', 'indent_size = 6'].join('\\n')\n    );\n    expect(resolveEditorConfig(path.join(tmpDir, 'test.hbs')).indent_size).toBe(6);\n    expect(resolveEditorConfig(path.join(tmpDir, 'test.gjs')).indent_size).toBe(6);\n    expect(resolveEditorConfig(path.join(tmpDir, 'test.js')).indent_size).toBeUndefined();\n  });\n\n  it('later sections override earlier ones', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*]', 'indent_size = 2', '', '[*.hbs]', 'indent_size = 4'].join('\\n')\n    );\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_size).toBe(4);\n  });\n\n  it('inner .editorconfig overrides outer', () => {\n    // outer\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*]', 'indent_size = 2'].join('\\n')\n    );\n    // inner dir\n    const innerDir = path.join(tmpDir, 'app');\n    fs.mkdirSync(innerDir);\n    fs.writeFileSync(path.join(innerDir, '.editorconfig'), ['[*]', 'indent_size = 4'].join('\\n'));\n    const filePath = path.join(innerDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_size).toBe(4);\n  });\n\n  it('sets indent_size to tab when indent_style is tab and indent_size unset', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '', '[*]', 'indent_style = tab'].join('\\n')\n    );\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_style).toBe('tab');\n    expect(result.indent_size).toBe('tab');\n  });\n\n  it('ignores comments', () => {\n    fs.writeFileSync(\n      path.join(tmpDir, '.editorconfig'),\n      ['root = true', '# a comment', '; another comment', '[*]', 'indent_size = 5'].join('\\n')\n    );\n    const filePath = path.join(tmpDir, 'test.hbs');\n    const result = resolveEditorConfig(filePath);\n    expect(result.indent_size).toBe(5);\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/ember-source-version-test.js",
    "content": "'use strict';\n\nconst fs = require('node:fs');\nconst os = require('node:os');\nconst path = require('node:path');\nconst { isEmberSourceVersionAtLeast } = require('../../../lib/utils/ember-source-version');\n\ndescribe('ember-source-version', () => {\n  describe('isEmberSourceVersionAtLeast', () => {\n    let tmpDir;\n\n    beforeEach(() => {\n      tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-plugin-ember-'));\n    });\n\n    afterEach(() => {\n      fs.rmSync(tmpDir, { recursive: true });\n    });\n\n    it('returns true when installed version meets the requirement', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '6.8.0' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(true);\n    });\n\n    it('returns true when installed version exceeds the requirement', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '7.0.0' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(true);\n    });\n\n    it('returns true when major version is greater', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '7.0.0' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(true);\n    });\n\n    it('returns true when major matches and minor is greater', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '6.10.0' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(true);\n    });\n\n    it('returns true with patch version', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '6.8.3' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(true);\n    });\n\n    it('returns true with pre-release version that meets requirement', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '6.8.0-beta.1' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(true);\n    });\n\n    it('returns false when installed version is below the requirement', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '6.7.0' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(false);\n    });\n\n    it('returns false when major is less', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source', version: '5.12.0' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(false);\n    });\n\n    it('returns false when ember-source is not installed', () => {\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(false);\n    });\n\n    it('returns false when package.json has no version field', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(\n        path.join(emberSourceDir, 'package.json'),\n        JSON.stringify({ name: 'ember-source' })\n      );\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(false);\n    });\n\n    it('returns false when package.json is malformed', () => {\n      const emberSourceDir = path.join(tmpDir, 'node_modules', 'ember-source');\n      fs.mkdirSync(emberSourceDir, { recursive: true });\n      fs.writeFileSync(path.join(emberSourceDir, 'package.json'), 'not json');\n\n      expect(isEmberSourceVersionAtLeast(6, 8, tmpDir)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/ember-test.js",
    "content": "'use strict';\n\nconst { parse: babelESLintParse, parseForESLint } = require('../../helpers/babel-eslint-parser');\nconst emberUtils = require('../../../lib/utils/ember');\nconst { FauxContext } = require('../../helpers/faux-context');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0].expression;\n}\n\nfunction getProperty(code) {\n  return parse(code).right.properties[0];\n}\n\ndescribe('isModule', () => {\n  let node;\n\n  it(\"should check if it's a proper module\", () => {\n    node = parse('Ember.test()');\n    expect(emberUtils.isModule(node, 'test')).toBeTruthy();\n\n    node = parse('Ember.Component()');\n    expect(emberUtils.isModule(node, 'Component')).toBeTruthy();\n\n    node = parse('DS.test()');\n    expect(emberUtils.isModule(node, 'test', 'DS')).toBeTruthy();\n\n    node = parse('DS.Model()');\n    expect(emberUtils.isModule(node, 'Model', 'DS')).toBeTruthy();\n  });\n});\n\ndescribe('isDSModel', () => {\n  it(\"should check if it's a DS Model\", () => {\n    const node = parse('DS.Model()');\n\n    expect(emberUtils.isDSModel(node)).toBeTruthy();\n  });\n\n  describe(\"should check if it's a DS Model even if it uses custom name\", () => {\n    it(\"shouldn't detect Model when no file path is provided\", () => {\n      const node = parse('CustomModel.extend()');\n      expect(emberUtils.isDSModel(node)).toBeFalsy();\n    });\n\n    it('should detect Model when file path is provided', () => {\n      const node = parse('CustomModel.extend()');\n      const filePath = 'example-app/models/path/to/some-model.js';\n      expect(emberUtils.isDSModel(node, filePath)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isEmberDataModel', () => {\n  it('detects Model imported from @ember-data/model in native classes', () => {\n    const context = new FauxContext(`\n      import Model from '@ember-data/model';\n      class UserModel extends Model {}\n    `);\n    const node = context.ast.body[1];\n\n    expect(emberUtils.isEmberDataModel(context, node)).toBeTruthy();\n  });\n\n  it('detects Model imported from ember-data/model in native classes', () => {\n    const context = new FauxContext(`\n      import Model from 'ember-data/model';\n      class UserModel extends Model {}\n    `);\n    const node = context.ast.body[1];\n\n    expect(emberUtils.isEmberDataModel(context, node)).toBeTruthy();\n  });\n\n  it('detects native classes with mixin extension syntax', () => {\n    const context = new FauxContext(`\n      import Model from '@ember-data/model';\n      class UserModel extends Model.extend(SoftDeleteMixin) {}\n    `);\n    const node = context.ast.body[1];\n\n    expect(emberUtils.isEmberDataModel(context, node)).toBeTruthy();\n  });\n\n  it('detects model class expressions', () => {\n    const context = new FauxContext(`\n      import Model from '@ember-data/model';\n      const UserModel = class extends Model {};\n    `);\n    const node = context.ast.body[1].declarations[0].init;\n\n    expect(emberUtils.isEmberDataModel(context, node)).toBeTruthy();\n  });\n\n  it('does not detect native classes when import path is incorrect', () => {\n    const context = new FauxContext(`\n      import Model from '@somewhere-else/model';\n      class UserModel extends Model {}\n    `);\n    const node = context.ast.body[1];\n\n    expect(emberUtils.isEmberDataModel(context, node)).toBeFalsy();\n  });\n\n  it('detects model class by file path when import source is unknown', () => {\n    const context = new FauxContext(\n      `\n      class UserModel extends LocalModel {}\n    `,\n      'example-app/models/path/to/user.js'\n    );\n    const node = context.ast.body[0];\n\n    expect(emberUtils.isEmberDataModel(context, node)).toBeTruthy();\n  });\n\n  it('throws when called on wrong type of node', () => {\n    const context = new FauxContext('const x = 123;');\n    const node = context.ast.body[0];\n\n    expect(() => emberUtils.isEmberDataModel(context, node)).toThrow(\n      'Function should only be called on a `ClassDeclaration`/`ClassExpression` (native class)'\n    );\n  });\n});\n\ndescribe('isModuleByFilePath', () => {\n  it('should check if current file is a component', () => {\n    const filePath = 'example-app/components/path/to/some-component.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeTruthy();\n  });\n\n  it('should check if current file is a component in PODs structure', () => {\n    const filePath = 'example-app/components/path/to/some-component/component.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeTruthy();\n  });\n\n  it('should check if current file is a controller', () => {\n    const filePath = 'example-app/controllers/path/to/some-feature.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'controller')).toBeTruthy();\n  });\n\n  it('should check if current file is a controller in PODs structure', () => {\n    const filePath = 'example-app/path/to/some-feature/controller.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'controller')).toBeTruthy();\n  });\n\n  it('should check if current file is a route', () => {\n    const filePath = 'example-app/routes/path/to/some-feature.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'route')).toBeTruthy();\n  });\n\n  it('should check if current file is a route - PODs structure', () => {\n    const filePath = 'example-app/routes/path/to/some-feature/route.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'route')).toBeTruthy();\n  });\n\n  it('should handle TypeScript files', () => {\n    const filePath = 'example-app/components/path/to/some-component.ts';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeTruthy();\n  });\n\n  // Avoid false positives:\n  it('should not detect a component in a folder named `fake-components`', () => {\n    const filePath = 'example-app/fake-components/path/to/file.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeFalsy();\n  });\n\n  it('should not detect a component with a file named `components`', () => {\n    const filePath = 'example-app/some-folder/path/to/components';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeFalsy();\n  });\n\n  it('should not detect a component with a directory named `component.js`', () => {\n    const filePath = 'example-app/component.js/path/to/file.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeFalsy();\n  });\n\n  it('should not detect a component with a directory named `component.ts`', () => {\n    const filePath = 'example-app/component.ts/path/to/file.js';\n    expect(emberUtils.isModuleByFilePath(filePath, 'component')).toBeFalsy();\n  });\n});\n\ndescribe('isMirageDirectory', () => {\n  it('detects the mirage directory', () => {\n    expect(emberUtils.isMirageDirectory('example-app/mirage/config.js')).toBeTruthy();\n    expect(emberUtils.isMirageDirectory('example-app/mirage/scenarios/foo.js')).toBeTruthy();\n  });\n\n  it('does not detect other directories', () => {\n    expect(emberUtils.isMirageDirectory('example-app/app/app.js')).toBeFalsy();\n    expect(emberUtils.isMirageDirectory('example-app/tests/test.js')).toBeFalsy();\n    expect(emberUtils.isMirageDirectory('example-addon/addon/addon.js')).toBeFalsy();\n  });\n});\n\ndescribe('isMirageConfig', () => {\n  it('detects the mirage config file', () => {\n    expect(emberUtils.isMirageConfig('example-app/mirage/config.js')).toBeTruthy();\n  });\n\n  it('does not detect other directories', () => {\n    expect(emberUtils.isMirageConfig('example-app/app/app.js')).toBeFalsy();\n    expect(emberUtils.isMirageConfig('example-app/tests/test.js')).toBeFalsy();\n    expect(emberUtils.isMirageConfig('example-addon/addon/addon.js')).toBeFalsy();\n    expect(emberUtils.isMirageConfig('example-app/mirage/scenarios/foo.js')).toBeFalsy();\n  });\n});\n\ndescribe('isTestFile', () => {\n  it('detects test files ending with -test', () => {\n    expect(emberUtils.isTestFile('some-test.js')).toBeTruthy();\n    expect(emberUtils.isTestFile('some-test.ts')).toBeTruthy();\n    expect(emberUtils.isTestFile('some-test.gjs')).toBeTruthy();\n    expect(emberUtils.isTestFile('some-test.gts')).toBeTruthy();\n  });\n\n  it('detects test files ending with _test', () => {\n    expect(emberUtils.isTestFile('some_test.js')).toBeTruthy();\n    expect(emberUtils.isTestFile('some_test.ts')).toBeTruthy();\n    expect(emberUtils.isTestFile('some_test.gjs')).toBeTruthy();\n    expect(emberUtils.isTestFile('some_test.gts')).toBeTruthy();\n  });\n\n  it('detects test files ending with .test', () => {\n    expect(emberUtils.isTestFile('some.test.js')).toBeTruthy();\n    expect(emberUtils.isTestFile('some.test.ts')).toBeTruthy();\n    expect(emberUtils.isTestFile('some.test.gjs')).toBeTruthy();\n    expect(emberUtils.isTestFile('some.test.gts')).toBeTruthy();\n  });\n\n  it('does not detect other files', () => {\n    expect(emberUtils.isTestFile('some-component.js')).toBeFalsy();\n    expect(emberUtils.isTestFile('my-testing-component.js')).toBeFalsy();\n    expect(emberUtils.isTestFile('router.js')).toBeFalsy();\n    expect(emberUtils.isTestFile('my-test.html')).toBeFalsy();\n  });\n});\n\ndescribe('isEmberCoreModule', () => {\n  it('should check if current file is a component (custom)', () => {\n    const context = new FauxContext(\n      'CustomComponent.extend()',\n      'example-app/components/path/to/some-component.js'\n    );\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Component')).toBeTruthy();\n  });\n\n  it('should check if current file is a component', () => {\n    const context = new FauxContext(\n      'Component.extend()',\n      'example-app/some-twisted-path/some-component.js'\n    );\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Component')).toBeTruthy();\n  });\n\n  it('should check if current file is a controller (custom)', () => {\n    const context = new FauxContext(\n      'CustomController.extend()',\n      'example-app/controllers/path/to/some-controller.js'\n    );\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Controller')).toBeTruthy();\n  });\n\n  it('should check if current file is a controller', () => {\n    const context = new FauxContext(\n      'Controller.extend()',\n      'example-app/some-twisted-path/some-controller.js'\n    );\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Controller')).toBeTruthy();\n  });\n\n  it('should check if current file is a route (custom)', () => {\n    const context = new FauxContext(\n      'CustomRoute.extend()',\n      'example-app/routes/path/to/some-route.js'\n    );\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy();\n  });\n\n  it('should check if current file is a route', () => {\n    const context = new FauxContext(\n      'Route.extend()',\n      'example-app/some-twisted-path/some-route.js'\n    );\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy();\n  });\n\n  it('should handle native class with mixin', () => {\n    const context = new FauxContext(\n      \"import Route from '@ember/routing/route'; class MyRoute extends Route.extend(SomeMixin) {}\",\n      'example-app/routes/path/to/some-route.js'\n    );\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy();\n  });\n\n  it('should check if current file is a route with native class', () => {\n    const context = new FauxContext(\n      \"import Route from '@ember/routing/route'; class MyRoute extends Route {}\",\n      'example-app/some-twisted-path/some-route.js'\n    );\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy();\n  });\n\n  it('should check core modules from ClassExpressions', () => {\n    const context = new FauxContext(\n      `import Route from '@ember/routing/route';\n\n      (class MyRoute extends Route {})`,\n      'example-app/some-twisted-path/some-route.js'\n    );\n    const node = context.ast.body[1].expression;\n    expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeTruthy();\n  });\n\n  it('ignores a native class with a non-identifier super class', () => {\n    const context = new FauxContext(\n      'class MyRoute extends this.ContainerObject {}',\n      'example-app/some-twisted-path/some-route.js'\n    );\n    const node = context.ast.body[0];\n    expect(emberUtils.isEmberCoreModule(context, node, 'Route')).toBeFalsy();\n  });\n\n  it('throws when called on wrong type of node', () => {\n    const context = new FauxContext('const x = 123;');\n    const node = context.ast.body[0];\n    expect(() => emberUtils.isEmberCoreModule(context, node, 'Route')).toThrow(\n      'Function should only be called on a `CallExpression` (classic class) or `ClassDeclaration`/`ClassExpression` (native class)'\n    );\n  });\n});\n\ndescribe('isEmberComponent', () => {\n  describe(\"should check if it's an Ember Component\", () => {\n    it('should detect Component when using Ember.Component', () => {\n      const context = new FauxContext('Ember.Component.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberComponent(context, node)).toBeTruthy();\n    });\n\n    it('should detect Component when using local module Component', () => {\n      const context = new FauxContext('Component.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberComponent(context, node)).toBeTruthy();\n    });\n\n    it('should detect Component when using native classes', () => {\n      const context = new FauxContext(`\n        import Component from '@ember/component';\n        class MyComponent extends Component {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberComponent(context, node)).toBeTruthy();\n    });\n\n    it('shouldnt detect Component when using native classes if the import path is incorrect', () => {\n      const context = new FauxContext(`\n        import Component from '@something-else/component';\n        class MyComponent extends Component {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberComponent(context, node)).toBeFalsy();\n    });\n  });\n\n  describe(\"should check if it's an Ember Component even if it uses custom name\", () => {\n    it(\"shouldn't detect Component when no file path is provided\", () => {\n      const context = new FauxContext('CustomComponent.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberComponent(context, node)).toBeFalsy();\n    });\n\n    it('should detect Component when file path is provided', () => {\n      const context = new FauxContext(\n        'CustomComponent.extend()',\n        'example-app/components/path/to/some-component.js'\n      );\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberComponent(context, node)).toBeTruthy();\n    });\n\n    it('should detect Component when using native classes if the import path is correct', () => {\n      const context = new FauxContext(`\n        import CustomComponent from '@ember/component';\n        class MyComponent extends CustomComponent {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberComponent(context, node)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isGlimerComponent', () => {\n  describe(\"should check if it's a Glimmer Component\", () => {\n    it('should detect Component when using native classes', () => {\n      const context = new FauxContext(`\n        import Component from '@glimmer/component';\n        class MyComponent extends Component {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isGlimmerComponent(context, node)).toBeTruthy();\n    });\n\n    it('shouldnt detect Component when using native classes if the import path is incorrect', () => {\n      const context = new FauxContext(`\n        import Component from '@something-else/component';\n        class MyComponent extends Component {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isGlimmerComponent(context, node)).toBeFalsy();\n    });\n\n    it('shouldnt confuse Glimmer Components with Ember Components', () => {\n      const context = new FauxContext(`\n        import Component from '@ember/component';\n        class MyComponent extends Component {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isGlimmerComponent(context, node)).toBeFalsy();\n    });\n  });\n\n  describe(\"should check if it's a Glimmer Component even if it uses a custom name\", () => {\n    it('should detect Component when using native classes if the import path is correct', () => {\n      const context = new FauxContext(`\n        import CustomComponent from '@glimmer/component';\n        class MyComponent extends CustomComponent {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isGlimmerComponent(context, node)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isEmberController', () => {\n  describe(\"should check if it's an Ember Controller\", () => {\n    it('should detect Controller when using Ember.Controller', () => {\n      const context = new FauxContext('Ember.Controller.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberController(context, node)).toBeTruthy();\n    });\n\n    it('should detect Controller when using local module Controller', () => {\n      const context = new FauxContext('Controller.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberController(context, node)).toBeTruthy();\n    });\n\n    it('should detect Controller when using native classes', () => {\n      const context = new FauxContext(`\n        import Controller from '@ember/controller';\n        class MyController extends Controller {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberController(context, node)).toBeTruthy();\n    });\n\n    it('shouldnt detect Controller when using native classes if the import path is incorrect', () => {\n      const context = new FauxContext(`\n        import Controller from '@something-else/controller';\n        class MyController extends Controller {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberController(context, node)).toBeFalsy();\n    });\n  });\n\n  describe(\"should check if it's an Ember Controller even if it uses custom name\", () => {\n    it(\"shouldn't detect Controller when no file path is provided\", () => {\n      const context = new FauxContext('CustomController.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberController(context, node)).toBeFalsy();\n    });\n\n    it('should detect Controller when file path is provided', () => {\n      const context = new FauxContext(\n        'CustomController.extend()',\n        'example-app/controllers/path/to/some-feature.js'\n      );\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberController(context, node)).toBeTruthy();\n    });\n\n    it('should detect Controller when using native classes if the import path is correct', () => {\n      const context = new FauxContext(`\n        import CustomController from '@ember/controller';\n        class MyController extends CustomController {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberController(context, node)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isEmberRoute', () => {\n  describe(\"should check if it's an Ember Route\", () => {\n    it('should detect Route when using Ember.Route', () => {\n      const context = new FauxContext('Ember.Route.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberRoute(context, node)).toBeTruthy();\n    });\n\n    it('should detect Route when using local module Route', () => {\n      const context = new FauxContext('Route.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberRoute(context, node)).toBeTruthy();\n    });\n\n    it('should detect Route when using native classes', () => {\n      const context = new FauxContext(`\n        import Route from '@ember/routing/route';\n        class MyRoute extends Route {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberRoute(context, node)).toBeTruthy();\n    });\n\n    it('shouldnt detect Route when using native classes if the import path is incorrect', () => {\n      const context = new FauxContext(`\n        import Route from '@something-else/routing/route';\n        class MyRoute extends Route {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberRoute(context, node)).toBeFalsy();\n    });\n  });\n\n  describe(\"should check if it's an Ember Route even if it uses custom name\", () => {\n    it(\"shouldn't detect Route when no file path is provided\", () => {\n      const context = new FauxContext('CustomRoute.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberRoute(context, node)).toBeFalsy();\n    });\n\n    it('should detect Route when file path is provided', () => {\n      const context = new FauxContext(\n        'CustomRoute.extend()',\n        'example-app/routes/path/to/some-feature.js'\n      );\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberRoute(context, node)).toBeTruthy();\n    });\n\n    it('should detect Route when using native classes if the import path is correct', () => {\n      const context = new FauxContext(`\n        import CustomRoute from '@ember/routing/route';\n        class MyRoute extends CustomRoute {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberRoute(context, node)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isEmberMixin', () => {\n  describe(\"should check if it's an Ember Mixin\", () => {\n    it('should detect Mixin when using native classes', () => {\n      const context = new FauxContext(`\n        import Mixin from '@ember/object/mixin';\n        class MyMixin extends Mixin {}\n      `);\n\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberMixin(context, node)).toBeTruthy();\n    });\n\n    it('shouldnt detect Mixin when using native classes if the import path is incorrect', () => {\n      const context = new FauxContext(`\n        import Mixin from '@something-else/object/mixin';\n        class MyMixin extends Mixin {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberMixin(context, node)).toBeFalsy();\n    });\n  });\n\n  describe(\"should check if it's an Ember Mixin even if it uses custom name\", () => {\n    it('should detect Mixin when using native classes if the import path is correct', () => {\n      const context = new FauxContext(`\n        import CustomMixin from '@ember/object/mixin';\n        class MyMixin extends CustomMixin {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberMixin(context, node)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isEmberService', () => {\n  describe(\"should check if it's an Ember Service\", () => {\n    it('should detect Service when using Ember.Service', () => {\n      const context = new FauxContext('Ember.Service.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberService(context, node)).toBeTruthy();\n    });\n\n    it('should detect Service when using local module Service', () => {\n      const context = new FauxContext('Service.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberService(context, node)).toBeTruthy();\n    });\n\n    it('should detect Service when using native classes', () => {\n      const context = new FauxContext(`\n        import Service from '@ember/service';\n        class MyService extends Service {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberService(context, node)).toBeTruthy();\n    });\n\n    it('shouldnt detect Service when using native classes if the import path is incorrect', () => {\n      const context = new FauxContext(`\n        import Service from '@something-else/service';\n        class MyService extends Service {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberService(context, node)).toBeFalsy();\n    });\n  });\n\n  describe(\"should check if it's an Ember Service even if it uses custom name\", () => {\n    it(\"shouldn't detect Service when no file path is provided\", () => {\n      const context = new FauxContext('CustomService.extend()');\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberService(context, node)).toBeFalsy();\n    });\n\n    it('should detect Service when file path is provided', () => {\n      const context = new FauxContext(\n        'CustomService.extend()',\n        'example-app/services/path/to/some-feature.js'\n      );\n      const node = context.ast.body[0].expression;\n      expect(emberUtils.isEmberService(context, node)).toBeTruthy();\n    });\n\n    it('should detect Service when using native classes if the import path is correct', () => {\n      const context = new FauxContext(`\n        import CustomService from '@ember/service';\n        class MyService extends CustomService {}\n      `);\n      const node = context.ast.body[1];\n      expect(emberUtils.isEmberService(context, node)).toBeTruthy();\n    });\n  });\n});\n\ndescribe('isExtendObject', () => {\n  it('should detect using extend function name', () => {\n    const node = parse('foo.extend()');\n    expect(emberUtils.isExtendObject(node)).toBeTruthy();\n  });\n\n  it('should detect using extend string name', () => {\n    const node = parse('foo[\"extend\"]()');\n    expect(emberUtils.isExtendObject(node)).toBeTruthy();\n  });\n\n  it('should detect using nested object', () => {\n    const node = parse('foo.bar.extend()');\n    expect(emberUtils.isExtendObject(node)).toBeTruthy();\n  });\n\n  it('should not detect a potential jQuery usage with `$`', () => {\n    const node = parse('$.extend()');\n    expect(emberUtils.isExtendObject(node)).toBeFalsy();\n  });\n\n  it('should not detect a potential jQuery usage with `jQuery`', () => {\n    const node = parse('jQuery.extend()');\n    expect(emberUtils.isExtendObject(node)).toBeFalsy();\n  });\n\n  it('should not detect a potential lodash usage', () => {\n    expect(emberUtils.isExtendObject(parse('_.extend()'))).toBeFalsy();\n    expect(emberUtils.isExtendObject(parse('lodash.extend()'))).toBeFalsy();\n  });\n\n  it('should not detect with non-extend name', () => {\n    const node = parse('foo.notExtend()');\n    expect(emberUtils.isExtendObject(node)).toBeFalsy();\n  });\n\n  it('should not detect with no object', () => {\n    const node = parse('extend()');\n    expect(emberUtils.isExtendObject(node)).toBeFalsy();\n  });\n\n  it('should not detect with wrong function', () => {\n    const node = parse('extend.foo()');\n    expect(emberUtils.isExtendObject(node)).toBeFalsy();\n  });\n});\n\ndescribe('isEmberArrayProxy', () => {\n  it('should detect using old module style', () => {\n    const context = new FauxContext('Ember.ArrayProxy.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect using old module style with wrong name', () => {\n    const context = new FauxContext('Ember.SomethingElse.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeFalsy();\n  });\n\n  it('should detect when using local module', () => {\n    const context = new FauxContext('ArrayProxy.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using local module with wrong name', () => {\n    const context = new FauxContext('SomethingElse.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeFalsy();\n  });\n\n  it('should detect when using native classes', () => {\n    const context = new FauxContext(`\n      import ArrayProxy from '@ember/array/proxy';\n      class MyProxy extends ArrayProxy {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeTruthy();\n  });\n\n  it('should detect when using native classes with other name but correct import path', () => {\n    const context = new FauxContext(`\n      import OtherName from '@ember/array/proxy';\n      class MyProxy extends OtherName {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using native classes if the import path is incorrect', () => {\n    const context = new FauxContext(`\n      import ArrayProxy from '@something-else/service';\n      class MyProxy extends ArrayProxy {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberArrayProxy(context, node)).toBeFalsy();\n  });\n});\n\ndescribe('isEmberObjectProxy', () => {\n  it('should detect using old module style', () => {\n    const context = new FauxContext('Ember.ObjectProxy.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect using old module style with wrong name', () => {\n    const context = new FauxContext('Ember.SomethingElse.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeFalsy();\n  });\n\n  it('should detect when using local module', () => {\n    const context = new FauxContext('ObjectProxy.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using local module with wrong name', () => {\n    const context = new FauxContext('SomethingElse.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeFalsy();\n  });\n\n  it('should detect when using native classes', () => {\n    const context = new FauxContext(`\n      import ObjectProxy from '@ember/object/proxy';\n      class MyProxy extends ObjectProxy {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeTruthy();\n  });\n\n  it('should detect when using native classes with other name but correct import path', () => {\n    const context = new FauxContext(`\n      import OtherName from '@ember/object/proxy';\n      class MyProxy extends OtherName {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using native classes if the import path is incorrect', () => {\n    const context = new FauxContext(`\n      import ObjectProxy from '@something-else/service';\n      class MyProxy extends ObjectProxy {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberObjectProxy(context, node)).toBeFalsy();\n  });\n});\n\ndescribe('isEmberObject', () => {\n  it('should detect when using local module', () => {\n    const context = new FauxContext('EmberObject.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberObject(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using local module with wrong name', () => {\n    const context = new FauxContext('SomethingElse.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberObject(context, node)).toBeFalsy();\n  });\n\n  it('should detect when using native classes', () => {\n    const context = new FauxContext(`\n      import EmberObject from '@ember/object';\n      class MyObject extends EmberObject {}\n    `);\n\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberObject(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using native classes if the import path is incorrect', () => {\n    const context = new FauxContext(`\n      import EmberObject from '@something-else/object';\n      class MyObject extends EmberObject {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberObject(context, node)).toBeFalsy();\n  });\n});\n\ndescribe('isEmberHelper', () => {\n  it('should detect when using local module', () => {\n    const context = new FauxContext('Helper.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberHelper(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using local module with wrong name', () => {\n    const context = new FauxContext('SomethingElse.extend()');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberHelper(context, node)).toBeFalsy();\n  });\n\n  it('should detect when using native classes', () => {\n    const context = new FauxContext(`\n      import Helper from '@ember/component/helper';\n      class MyHelper extends Helper {}\n    `);\n\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberHelper(context, node)).toBeTruthy();\n  });\n\n  it('should not detect when using native classes if the import path is incorrect', () => {\n    const context = new FauxContext(`\n      import Helper from '@something-else/component/helper';\n      class MyHelper extends Helper {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberHelper(context, node)).toBeFalsy();\n  });\n});\n\ndescribe('isEmberProxy', () => {\n  it('should detect ArrayProxy example', () => {\n    const context = new FauxContext(`\n      import ArrayProxy from '@ember/array/proxy';\n      class MyProxy extends ArrayProxy {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberProxy(context, node)).toBeTruthy();\n  });\n\n  it('should detect ObjectProxy example', () => {\n    const context = new FauxContext(`\n      import ObjectProxy from '@ember/object/proxy';\n      class MyProxy extends ObjectProxy {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberProxy(context, node)).toBeTruthy();\n  });\n\n  it('should detect ObjectProxy with mixin', () => {\n    const context = new FauxContext(`\n      import ObjectProxy from '@ember/object/proxy';\n      class MyProxy extends ObjectProxy.extend(SomeMixin) {}\n    `);\n    const node = context.ast.body[1];\n    expect(emberUtils.isEmberProxy(context, node)).toBeTruthy();\n  });\n\n  it('should detect ObjectProxy with classic class', () => {\n    const context = new FauxContext(`\n      import ObjectProxy from '@ember/object/proxy';\n      ObjectProxy.extend({});\n    `);\n    const node = context.ast.body[1].expression;\n    expect(emberUtils.isEmberProxy(context, node)).toBeTruthy();\n  });\n\n  it('should not detect random code', () => {\n    const context = new FauxContext('someFunctionCall();');\n    const node = context.ast.body[0].expression;\n    expect(emberUtils.isEmberProxy(context, node)).toBeFalsy();\n  });\n});\n\ndescribe('isInjectedServiceProp', () => {\n  describe('classic classes', () => {\n    it(\"should check if it's an injected service prop with renamed import\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          currentUser: service()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeTruthy();\n    });\n\n    it(\"should check if it's an injected service prop with full import\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          currentUser: Ember.inject.service()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, importName, undefined)).toBeTruthy();\n    });\n\n    it(\"should check if it's an injected service prop with destructured import\", () => {\n      const context = new FauxContext(`\n        import {inject} from '@ember/service';\n        export default Controller.extend({\n          currentUser: inject()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeTruthy();\n    });\n\n    it(\"should check that it's not an injected service prop with foo.service\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          currentUser: foo.service()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop with foo.service.inject\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          currentUser: foo.service.inject()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop without the renamed import\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          currentUser: service()\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop without the full import\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          currentUser: Ember.inject.service()\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop with Ember.inject\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          currentUser: Ember.inject()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, importName, undefined)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop with Ember.inject.foo\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          currentUser: Ember.inject.foo()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, importName, undefined)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop with Ember.foo.service\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          currentUser: Ember.foo.service()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, importName, undefined)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop with Ember.service.foo\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          currentUser: Ember.service.foo()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, importName, undefined)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          currentUser: otherFunction()\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop when 'service' is not a function\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          currentUser: service.otherFunction()\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeFalsy();\n    });\n  });\n\n  describe('native classes', () => {\n    it(\"should check if it's an injected service prop when using renamed import\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        class MyController extends Controller {\n          @service currentUser;\n        }\n      `);\n      const node = context.ast.body[1].body.body[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, 'service')).toBeTruthy();\n    });\n\n    it(\"should check if it's an injected service prop when service is from another object\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        class MyController extends Controller {\n          @foo.service currentUser;\n        }\n      `);\n      const node = context.ast.body[1].body.body[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, 'service')).toBeFalsy();\n    });\n\n    it(\"should check if it's an injected service prop when another function from service\", () => {\n      const context = new FauxContext(`\n        import {inject as service} from '@ember/service';\n        class MyController extends Controller {\n          @service.foo currentUser;\n        }\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeFalsy();\n    });\n\n    it(\"should check if it's an injected service prop when using decorator\", () => {\n      const context = new FauxContext(`\n        import {inject} from '@ember/service';\n        class MyController extends Controller {\n          @inject currentUser;\n        }\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(emberUtils.isInjectedServiceProp(node, undefined, importName)).toBeTruthy();\n    });\n\n    it(\"should check that it's not an injected service prop without an import\", () => {\n      const context = new FauxContext(`\n        class MyController extends Controller {\n          @service currentUser;\n        }\n      `);\n      const node = context.ast.body[0].body.body[0];\n      expect(emberUtils.isInjectedServiceProp(node)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected service prop when using another decorator\", () => {\n      const context = new FauxContext(`\n        class MyController extends Controller {\n          @otherDecorator currentUser;\n        }\n      `);\n      const node = context.ast.body[0].body.body[0];\n      expect(emberUtils.isInjectedServiceProp(node)).toBeFalsy();\n    });\n  });\n});\n\ndescribe('isInjectedControllerProp', () => {\n  describe('classic classes', () => {\n    it(\"should check if it's an injected controller prop with destructed import\", () => {\n      const context = new FauxContext(`\n        import {inject as controller} from '@ember/controller';\n        export default Controller.extend({\n          application: controller(),\n        });\n      `);\n      const importControllerName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(\n        emberUtils.isInjectedControllerProp(node, undefined, importControllerName)\n      ).toBeTruthy();\n    });\n\n    it(\"should check if it's an injected controller prop with full import\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          application: Ember.inject.controller(),\n        });\n      `);\n      const importEmberName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedControllerProp(node, importEmberName)).toBeTruthy();\n    });\n\n    it(\"should check if it's not an injected controller prop without import\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          application: controller(),\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedControllerProp(node)).toBeFalsy();\n    });\n\n    it(\"should check if it's not an injected controller prop without full import\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          application: Ember.inject.controller(),\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedControllerProp(node)).toBeFalsy();\n    });\n\n    it(\"should check if it's not an injected controller prop with foo.controller\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          application: foo.controller(),\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isInjectedControllerProp(node)).toBeFalsy();\n    });\n  });\n\n  describe('native classes', () => {\n    it(\"should check if it's an injected controller prop with decorator\", () => {\n      const context = new FauxContext(`\n        import {inject as controller} from '@ember/controller';\n        class MyController extends Controller {\n          @controller application;\n        }\n      `);\n      const importControllerName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(\n        emberUtils.isInjectedControllerProp(node, undefined, importControllerName)\n      ).toBeTruthy();\n    });\n\n    it(\"should check if it's not an injected controller prop with decorator without import\", () => {\n      const context = new FauxContext(`\n        class MyController extends Controller {\n          @controller application;\n        }\n      `);\n      const node = context.ast.body[0].body.body[0];\n      expect(emberUtils.isInjectedControllerProp(node)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an injected controller prop\", () => {\n      const context = new FauxContext(`\n        class MyController extends Controller {\n          @otherDecorator application;\n        }\n      `);\n      const node = context.ast.body[0].body.body[0];\n      expect(emberUtils.isInjectedControllerProp(node)).toBeFalsy();\n    });\n  });\n});\n\ndescribe('isComputedProp', () => {\n  let node;\n\n  it(\"should check if it's an computed prop\", () => {\n    node = parse('computed()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeTruthy();\n\n    node = parse('Ember.computed()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeTruthy();\n\n    node = parse('foo()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n\n    node = parse('Ember.foo()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n\n    node = parse('Foo.computed()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n\n    // Non-standard names:\n    node = parse('c()');\n    expect(emberUtils.isComputedProp(node, 'E', 'c')).toBeTruthy();\n\n    // Non-standard names:\n    node = parse('E.computed()');\n    expect(emberUtils.isComputedProp(node, 'E', 'c')).toBeTruthy();\n  });\n\n  it('should detect allow-listed computed props with MemberExpressions', () => {\n    for (const prop of ['volatile', 'meta', 'readOnly', 'property']) {\n      // With includeSuffix\n\n      node = parse(`computed().${prop}()`);\n      expect(\n        emberUtils.isComputedProp(node, 'Ember', 'computed', { includeSuffix: true })\n      ).toBeTruthy();\n\n      node = parse(`Ember.computed().${prop}()`);\n      expect(\n        emberUtils.isComputedProp(node, 'Ember', 'computed', { includeSuffix: true })\n      ).toBeTruthy();\n\n      // Without includeSuffix\n\n      node = parse(`computed().${prop}()`);\n      expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n\n      node = parse(`Ember.computed().${prop}()`);\n      expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n    }\n  });\n\n  it(\"shouldn't allow other MemberExpressions\", () => {\n    node = parse('computed().foo()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).not.toBeTruthy();\n\n    node = parse('Ember.computed().foo()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).not.toBeTruthy();\n\n    node = parse('computed.foo()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).not.toBeTruthy();\n\n    node = parse('Ember.computed.foo()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).not.toBeTruthy();\n  });\n\n  it('should detect the computed annotation', () => {\n    const program = babelESLintParse('class Object { @computed() get foo() {} }');\n    node = program.body[0].body.body[0].decorators[0].expression;\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeTruthy();\n  });\n\n  it('should detect the computed annotation without parentheses', () => {\n    const program = babelESLintParse('class Object { @computed get foo() {} }');\n    node = program.body[0].body.body[0].decorators[0].expression;\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeTruthy();\n  });\n\n  it('should not detect a sub-module decorator', () => {\n    const program = babelESLintParse('class Object { @computed.foo() get foo() {} }');\n    node = program.body[0].body.body[0].decorators[0].expression;\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n  });\n\n  it('should not detect the wrong decorator', () => {\n    const program = babelESLintParse('class Object { @foo() get foo() {} }');\n    node = program.body[0].body.body[0].decorators[0].expression;\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n  });\n\n  it('should detect macros', () => {\n    // With includeMacro\n\n    node = parse('computed.someMacro()');\n    expect(\n      emberUtils.isComputedProp(node, 'Ember', 'computed', { includeMacro: true })\n    ).toBeTruthy();\n\n    node = parse('Ember.computed.someMacro()');\n    expect(\n      emberUtils.isComputedProp(node, 'Ember', 'computed', { includeMacro: true })\n    ).toBeTruthy();\n\n    // Without includeMacro\n\n    node = parse('computed.someMacro()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n\n    node = parse('Ember.computed.someMacro()');\n    expect(emberUtils.isComputedProp(node, 'Ember', 'computed')).toBeFalsy();\n  });\n});\n\ndescribe('isObserverProp', () => {\n  describe('classic classes', () => {\n    it(\"should check if it's an observer prop using destructured import\", () => {\n      const context = new FauxContext(`\n        import {observer} from '@ember/object';\n        export default Controller.extend({\n          someObserver: observer(),\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isObserverProp(node, undefined, importName)).toBeTruthy();\n    });\n\n    it(\"should check if it's an observer prop with full import\", () => {\n      const context = new FauxContext(`\n        import Ember from 'ember';\n        export default Controller.extend({\n          someObserver: Ember.observer(),\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isObserverProp(node, importName)).toBeTruthy();\n    });\n\n    it(\"should check that it's not an observer prop without import\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          someObserver: observer(),\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isObserverProp(node)).toBeFalsy();\n    });\n\n    it(\"should check that it's not an observer prop without full import\", () => {\n      const context = new FauxContext(`\n        export default Controller.extend({\n          someObserver: Ember.observer(),\n        });\n      `);\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(emberUtils.isObserverProp(node)).toBeFalsy();\n    });\n\n    it(\"should check if it's an observer prop with multi-line observer\", () => {\n      const context = new FauxContext(`\n        import {observer} from '@ember/object';\n        export default Component.extend({\n          levelOfHappiness: observer(\"attitude\", \"health\", () => {\n          }),\n          vehicle: alias(\"car\")\n        });\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(emberUtils.isObserverProp(node, undefined, importName)).toBeTruthy();\n    });\n  });\n\n  describe('native classes', () => {\n    it(\"should check if it's an observer prop using decorator\", () => {\n      const context = new FauxContext(`\n        import {observer} from '@ember/object';\n        class MyController extends Controller {\n          @observer someObserver;\n        }\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(emberUtils.isObserverProp(node, undefined, importName)).toBeTruthy();\n    });\n\n    it(\"should check if it's an observer prop using decorator with arg\", () => {\n      const context = new FauxContext(`\n        import {observer} from '@ember/object';\n        class MyController extends Controller {\n          @observer(\"someArg\") someObserver() {};\n        }\n      `);\n      const importName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(emberUtils.isObserverProp(node, undefined, importName)).toBeTruthy();\n    });\n\n    it(\"should check that it's not an observer prop\", () => {\n      const context = new FauxContext(`\n        class MyController extends Controller {\n          @otherDecorator someObserver;\n        }\n      `);\n      const node = context.ast.body[0].body.body[0];\n      expect(emberUtils.isObserverProp(node)).toBeFalsy();\n    });\n  });\n});\n\ndescribe('isArrayProp', () => {\n  let node;\n\n  it('should be an array', () => {\n    node = getProperty('test = { test: new Ember.A() }');\n    expect(emberUtils.isArrayProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: new A() }');\n    expect(emberUtils.isArrayProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: [] }');\n    expect(emberUtils.isArrayProp(node)).toBeTruthy();\n  });\n});\n\ndescribe('isObjectProp', () => {\n  let node;\n\n  it('should be an object', () => {\n    node = getProperty('test = { test: new Ember.Object() }');\n    expect(emberUtils.isObjectProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: new Object() }');\n    expect(emberUtils.isObjectProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: {} }');\n    expect(emberUtils.isObjectProp(node)).toBeTruthy();\n  });\n});\n\ndescribe('isCustomProp', () => {\n  let node;\n\n  it('should be custom property', () => {\n    node = getProperty(\"test = { test: 'someLiteral' }\");\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty(\"test = { test: `foo${'bar'}` }\"); // eslint-disable-line no-template-curly-in-string\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: someIdentifier }');\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: [] }');\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: {} }');\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty(\"test = { test: foo ? 'bar': 'baz' }\");\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty('test = { test: hbs`lorem ipsum` }');\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n\n    node = getProperty('test = { actions: {} }');\n    expect(emberUtils.isCustomProp(node)).toBeFalsy();\n\n    node = getProperty('test = { test: -1 }');\n    expect(emberUtils.isCustomProp(node)).toBeTruthy();\n  });\n});\n\ndescribe('isRouteLifecycleHook', () => {\n  let node;\n\n  it('should be a route lifecycle hook', () => {\n    node = getProperty('test = { beforeModel() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { model() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { afterModel() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { serialize() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { redirect() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { activate() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { setupController() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { renderTemplate() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { resetController() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n\n    node = getProperty('test = { deactivate() {} }');\n    expect(emberUtils.isRouteLifecycleHook(node)).toBeTruthy();\n  });\n\n  it('should not crash when class field has no initializer', () => {\n    const classField = {\n      key: { name: 'beforeModel' },\n      value: null,\n    };\n\n    expect(() => emberUtils.isRouteLifecycleHook(classField)).not.toThrow();\n    expect(emberUtils.isRouteLifecycleHook(classField)).toBeFalsy();\n  });\n});\n\ndescribe('isActionsProp', () => {\n  const node = getProperty('test = { actions: {} }');\n\n  it('should be actions prop', () => {\n    expect(emberUtils.isActionsProp(node)).toBeTruthy();\n  });\n});\n\ndescribe('getModuleProperties', () => {\n  it(\"returns module's properties\", () => {\n    const code = `\n    Ember.Component.extend(SomeMixin, {\n      asd: 'qwe',\n      actions: {},\n      someMethod() {}\n    })`;\n    const parsed = parseForESLint(code);\n    const moduleNode = parsed.ast.body[0].expression;\n    const properties = emberUtils.getModuleProperties(moduleNode, parsed.scopeManager);\n    expect(properties).toHaveLength(3);\n  });\n\n  it(\"returns module's properties when object is not last argument\", () => {\n    const code = `\n    Ember.Component.extend({\n      asd: 'qwe',\n      actions: {},\n      someMethod() {}\n    }, SomeMixin)\n  `;\n    const parsed = parseForESLint(code);\n    const moduleNode = parsed.ast.body[0].expression;\n    const properties = emberUtils.getModuleProperties(moduleNode, parsed.scopeManager);\n    expect(properties).toHaveLength(3);\n  });\n\n  it(\"returns module's properties when there are multiple objects\", () => {\n    const code = `\n    Ember.Component.extend({\n      asd: 'qwe',\n      actions: {},\n      someMethod() {}\n    }, {\n      asd: 'abc'\n    })\n  `;\n    const parsed = parseForESLint(code);\n    const moduleNode = parsed.ast.body[0].expression;\n    const properties = emberUtils.getModuleProperties(moduleNode, parsed.scopeManager);\n    expect(properties).toHaveLength(4);\n  });\n\n  it(\"returns module's properties when object from a variable\", () => {\n    const code = `\n    const body = {\n      asd: 'qwe',\n      actions: {},\n      someMethod() {}\n    };\n    Ember.Component.extend(body)\n  `;\n    const parsed = parseForESLint(code);\n    const moduleNode = parsed.ast.body[1].expression;\n    const properties = emberUtils.getModuleProperties(moduleNode, parsed.scopeManager);\n    expect(properties).toHaveLength(3);\n  });\n});\n\ndescribe('isSingleLineFn', () => {\n  const property = getProperty(`test = {\n    test: computed.or('asd', 'qwe')\n  }`);\n\n  it('should check if given function has one line', () => {\n    expect(emberUtils.isSingleLineFn(property)).toBeTruthy();\n\n    let context = new FauxContext(`\n      class MyController extends Controller {\n        @computed(\"someProp\") someFunction() {}\n      }\n    `);\n    let node = context.ast.body[0].body.body[0];\n    expect(emberUtils.isSingleLineFn(node)).toBeTruthy();\n\n    context = new FauxContext(`\n      class MyController extends Controller {\n        @computed(\"someProp\") someFunction() {\n          console.log(\"hello\");\n        }\n      }\n    `);\n    node = context.ast.body[0].body.body[0];\n    expect(emberUtils.isSingleLineFn(node)).toBeFalsy();\n  });\n});\n\ndescribe('isMultiLineFn', () => {\n  const property = getProperty(`test = {\n    test: computed('asd', function() {\n      return get(this, 'asd') + 'test';\n    })\n  }`);\n\n  it('should check if given function has more than one line', () => {\n    expect(emberUtils.isMultiLineFn(property)).toBeTruthy();\n\n    let context = new FauxContext(`\n      class MyController extends Controller {\n        @computed(\"someProp\") someFunction() {\n          console.log(\"hello\");\n        }\n      }\n    `);\n    let node = context.ast.body[0].body.body[0];\n    expect(emberUtils.isMultiLineFn(node)).toBeTruthy();\n\n    context = new FauxContext(`\n      class MyController extends Controller {\n        @computed(\"someProp\") someFunction() {}\n      }\n    `);\n    node = context.ast.body[0].body.body[0];\n    expect(emberUtils.isMultiLineFn(node)).toBeFalsy();\n  });\n});\n\ndescribe('isFunctionExpression', () => {\n  let property;\n\n  it('should check if given property is a function expression', () => {\n    property = getProperty(`test = {\n      test: someFn(function() {})\n    }`);\n    expect(emberUtils.isFunctionExpression(property.value)).toBeTruthy();\n\n    property = getProperty(`test = {\n      test() {}\n    }`);\n    expect(emberUtils.isFunctionExpression(property.value)).toBeTruthy();\n\n    property = getProperty(`test = {\n      test: function() {}\n    }`);\n    expect(emberUtils.isFunctionExpression(property.value)).toBeTruthy();\n\n    property = getProperty(`test = {\n      test: () => {}\n    }`);\n    expect(emberUtils.isFunctionExpression(property.value)).toBeTruthy();\n\n    expect(emberUtils.isFunctionExpression(null)).toBeFalsy();\n  });\n});\n\ndescribe('isRelation', () => {\n  let property;\n\n  it('should detect hasMany relation', () => {\n    property = getProperty(`test = {\n      test: hasMany()\n    }`);\n    expect(emberUtils.isRelation(property)).toBeTruthy();\n\n    property = getProperty(`test = {\n      test: DS.hasMany()\n    }`);\n    expect(emberUtils.isRelation(property)).toBeTruthy();\n  });\n\n  it('should detect belongsTo relation', () => {\n    property = getProperty(`test = {\n      test: belongsTo()\n    }`);\n    expect(emberUtils.isRelation(property)).toBeTruthy();\n\n    property = getProperty(`test = {\n      test: DS.belongsTo()\n    }`);\n    expect(emberUtils.isRelation(property)).toBeTruthy();\n  });\n});\n\ndescribe('parseDependentKeys', () => {\n  it('should parse dependent keys from CallExpression', () => {\n    const node = parse(\"computed('model.{foo,bar}', 'model.bar')\");\n    expect(emberUtils.parseDependentKeys(node)).toStrictEqual([\n      'model.foo',\n      'model.bar',\n      'model.bar',\n    ]);\n  });\n\n  it('should work when no dependent keys present', () => {\n    const node = parse('computed(function() {})');\n    expect(emberUtils.parseDependentKeys(node)).toStrictEqual([]);\n  });\n\n  it('should handle dependent keys and function arguments', () => {\n    const node = parse(\"computed('model.{foo,bar}', 'model.bar', function() {})\");\n    expect(emberUtils.parseDependentKeys(node)).toStrictEqual([\n      'model.foo',\n      'model.bar',\n      'model.bar',\n    ]);\n  });\n\n  it('should handle dependent keys and function arguments in MemberExpression', () => {\n    const node = parse(`\n      computed('model.{foo,bar}', 'model.bar', function() {\n      }).volatile();\n    `);\n    expect(emberUtils.parseDependentKeys(node)).toStrictEqual([\n      'model.foo',\n      'model.bar',\n      'model.bar',\n    ]);\n  });\n});\n\ndescribe('unwrapBraceExpressions', () => {\n  it('should unwrap simple dependent keys', () => {\n    expect(emberUtils.unwrapBraceExpressions(['model.foo', 'model.bar'])).toStrictEqual([\n      'model.foo',\n      'model.bar',\n    ]);\n  });\n\n  it('should unwrap dependent keys with braces', () => {\n    expect(emberUtils.unwrapBraceExpressions(['model.{foo,bar}', 'model.bar'])).toStrictEqual([\n      'model.foo',\n      'model.bar',\n      'model.bar',\n    ]);\n  });\n\n  it('should unwrap more complex dependent keys', () => {\n    expect(\n      emberUtils.unwrapBraceExpressions(['model.{foo,bar}', 'model.bar', 'data.{foo,baz,qux}'])\n    ).toStrictEqual(['model.foo', 'model.bar', 'model.bar', 'data.foo', 'data.baz', 'data.qux']);\n  });\n\n  it('should unwrap multi-level keys', () => {\n    expect(\n      emberUtils.unwrapBraceExpressions(['model.bar.{foo,qux}', 'model.bar.baz'])\n    ).toStrictEqual(['model.bar.foo', 'model.bar.qux', 'model.bar.baz']);\n  });\n\n  it('should unwrap @each with extensions', () => {\n    expect(\n      emberUtils.unwrapBraceExpressions(['collection.@each.{foo,bar}', 'collection.@each.qux'])\n    ).toStrictEqual(['collection.@each.foo', 'collection.@each.bar', 'collection.@each.qux']);\n  });\n\n  it('should unwrap complicated mixed dependent keys', () => {\n    expect(emberUtils.unwrapBraceExpressions(['a.b.c.{d.@each.qwe.zxc,f,g.[]}'])).toStrictEqual([\n      'a.b.c.d.@each.qwe.zxc',\n      'a.b.c.f',\n      'a.b.c.g.[]',\n    ]);\n  });\n\n  it('should unwrap complicated mixed repeated dependent keys', () => {\n    expect(emberUtils.unwrapBraceExpressions(['a.b.{d.@each.qux,f,d.@each.foo}'])).toStrictEqual([\n      'a.b.d.@each.qux',\n      'a.b.f',\n      'a.b.d.@each.foo',\n    ]);\n  });\n});\n\ndescribe('hasDuplicateDependentKeys', () => {\n  it('reports duplicate dependent keys in computed calls', () => {\n    let node = parse(\"computed('model.{foo,bar}', 'model.bar')\");\n    expect(emberUtils.hasDuplicateDependentKeys(node, 'Ember', 'computed')).toBeTruthy();\n    node = parse(\"Ember.computed('model.{foo,bar}', 'model.bar')\");\n    expect(emberUtils.hasDuplicateDependentKeys(node, 'Ember', 'computed')).toBeTruthy();\n  });\n\n  it('ignores not repeated dependentKeys', () => {\n    let node = parse(\"computed('model.{foo,bar}', 'model.qux')\");\n    expect(emberUtils.hasDuplicateDependentKeys(node, 'Ember', 'computed')).not.toBeTruthy();\n    node = parse(\"Ember.computed('model.{foo,bar}', 'model.qux')\");\n    expect(emberUtils.hasDuplicateDependentKeys(node, 'Ember', 'computed')).not.toBeTruthy();\n    node = parse(\"computed('model.{foo,bar}', 'model.qux').volatile()\");\n    expect(emberUtils.hasDuplicateDependentKeys(node, 'Ember', 'computed')).not.toBeTruthy();\n  });\n\n  it('ignores non-computed calls', () => {\n    const node = parse(\"foo('model.{foo,bar}', 'model.bar')\");\n    expect(emberUtils.hasDuplicateDependentKeys(node, 'Ember', 'computed')).not.toBeTruthy();\n  });\n});\n\ndescribe('getEmberImportAliasName', () => {\n  it('should get the proper name of default import', () => {\n    const node = babelESLintParse(\"import foo from 'ember'\").body[0];\n    expect(emberUtils.getEmberImportAliasName(node)).toBe('foo');\n  });\n});\n\ndescribe('isEmberObjectImplementingUnknownProperty', () => {\n  it('should be true for a classic class EmberObject with `unknownProperty`', () => {\n    const node = babelESLintParse('EmberObject.extend({ unknownProperty() {} });').body[0]\n      .expression;\n    expect(emberUtils.isEmberObjectImplementingUnknownProperty(node)).toBeTruthy();\n  });\n\n  it('should be false for a classic class EmberObject without `unknownProperty`', () => {\n    const node = babelESLintParse('EmberObject.extend({ somethingElse() {} });').body[0].expression;\n    expect(emberUtils.isEmberObjectImplementingUnknownProperty(node)).toBeFalsy();\n  });\n\n  it('should be true for a native class EmberObject with `unknownProperty`', () => {\n    const node = babelESLintParse('class MyClass extends EmberObject { unknownProperty() {} }')\n      .body[0];\n    expect(emberUtils.isEmberObjectImplementingUnknownProperty(node)).toBeTruthy();\n  });\n\n  it('should be true for a classic class EmberObject with `unknownProperty` in an object variable', () => {\n    const parsed = parseForESLint(\n      'const body = { unknownProperty() {} }; EmberObject.extend(body);'\n    );\n    const node = parsed.ast.body[1].expression;\n    expect(\n      emberUtils.isEmberObjectImplementingUnknownProperty(node, parsed.scopeManager)\n    ).toBeTruthy();\n  });\n\n  it('should be false for a native class EmberObject without `unknownProperty`', () => {\n    const node = babelESLintParse('class MyClass extends EmberObject { somethingElse() {} }')\n      .body[0];\n    expect(emberUtils.isEmberObjectImplementingUnknownProperty(node)).toBeFalsy();\n  });\n\n  it('throws when called on wrong type of node', () => {\n    const node = babelESLintParse('const x = 123;').body[0];\n    expect(() => emberUtils.isEmberObjectImplementingUnknownProperty(node)).toThrow(\n      'Function should only be called on a `CallExpression` (classic class) or `ClassDeclaration` (native class)'\n    );\n  });\n});\n\ndescribe('isObserverDecorator', () => {\n  it('should be true for an observer decorator', () => {\n    const node = babelESLintParse(`\n    import { observes } from '@ember-decorators/object';\n    class FooComponent extends Component {\n      @observes('baz')\n      bar() {}\n    }`).body[1].body.body[0].decorators[0];\n    expect(emberUtils.isObserverDecorator(node, 'observes')).toBeTruthy();\n  });\n\n  it('should be true for an observer decorator with renamed import', () => {\n    const node = babelESLintParse(`\n    import { observes as observesRenamed } from '@ember-decorators/object';\n    class FooComponent extends Component {\n      @observesRenamed('baz')\n      bar() {}\n    }`).body[1].body.body[0].decorators[0];\n    expect(emberUtils.isObserverDecorator(node, 'observesRenamed')).toBeTruthy();\n  });\n\n  it('should be false for another type of decorator', () => {\n    const node = babelESLintParse(`\n    import { action } from '@ember/object';\n    class FooComponent extends Component {\n      @action\n      clickHandler() {}\n    }`).body[1].body.body[0].decorators[0];\n    expect(emberUtils.isObserverDecorator(node, 'observes')).toBeFalsy();\n  });\n\n  it('throws when called on a non-decorator', () => {\n    const node = babelESLintParse('const x = 123;').body[0];\n    expect(() => emberUtils.isObserverDecorator(node, 'observes')).toThrow(\n      'Should only call this function on a Decorator'\n    );\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/html-interactive-content-test.js",
    "content": "'use strict';\n\nconst { isHtmlInteractiveContent } = require('../../../lib/utils/html-interactive-content');\n\nfunction makeNode(tag, attrs = {}) {\n  return {\n    tag,\n    attributes: Object.entries(attrs).map(([name, value]) => {\n      if (value === true) {\n        return { name, value: { type: 'GlimmerTextNode', chars: '' } };\n      }\n      return { name, value: { type: 'GlimmerTextNode', chars: String(value) } };\n    }),\n  };\n}\n\nfunction getTextAttrValue(node, name) {\n  const attr = node.attributes && node.attributes.find((a) => a.name === name);\n  if (attr && attr.value && attr.value.type === 'GlimmerTextNode') {\n    return attr.value.chars;\n  }\n  return undefined;\n}\n\ndescribe('isHtmlInteractiveContent', () => {\n  describe('§3.2.5.2.7 unconditional interactive content', () => {\n    for (const tag of ['button', 'details', 'embed', 'iframe', 'label', 'select', 'textarea']) {\n      it(`<${tag}> is interactive`, () => {\n        expect(isHtmlInteractiveContent(makeNode(tag), getTextAttrValue)).toBe(true);\n      });\n    }\n  });\n\n  describe('<summary> (§4.11.2 — keyboard-activatable)', () => {\n    it('<summary> is interactive', () => {\n      expect(isHtmlInteractiveContent(makeNode('summary'), getTextAttrValue)).toBe(true);\n    });\n  });\n\n  describe('<input>', () => {\n    it('is interactive without type attribute (defaults to text)', () => {\n      expect(isHtmlInteractiveContent(makeNode('input'), getTextAttrValue)).toBe(true);\n    });\n\n    it('is interactive when type=\"text\"', () => {\n      expect(isHtmlInteractiveContent(makeNode('input', { type: 'text' }), getTextAttrValue)).toBe(\n        true\n      );\n    });\n\n    it('is NOT interactive when type=\"hidden\"', () => {\n      expect(\n        isHtmlInteractiveContent(makeNode('input', { type: 'hidden' }), getTextAttrValue)\n      ).toBe(false);\n    });\n\n    it('is NOT interactive when type=\"HIDDEN\" (case-insensitive)', () => {\n      expect(\n        isHtmlInteractiveContent(makeNode('input', { type: 'HIDDEN' }), getTextAttrValue)\n      ).toBe(false);\n    });\n\n    it('is NOT interactive when type=\" hidden \" (whitespace-trimmed)', () => {\n      expect(\n        isHtmlInteractiveContent(makeNode('input', { type: ' hidden ' }), getTextAttrValue)\n      ).toBe(false);\n    });\n  });\n\n  describe('<a>', () => {\n    it('is interactive when href is present', () => {\n      expect(isHtmlInteractiveContent(makeNode('a', { href: '/about' }), getTextAttrValue)).toBe(\n        true\n      );\n    });\n\n    it('is NOT interactive without href', () => {\n      expect(isHtmlInteractiveContent(makeNode('a'), getTextAttrValue)).toBe(false);\n    });\n  });\n\n  describe('<img>', () => {\n    it('is interactive when usemap is present', () => {\n      expect(isHtmlInteractiveContent(makeNode('img', { usemap: '#m' }), getTextAttrValue)).toBe(\n        true\n      );\n    });\n\n    it('is NOT interactive without usemap', () => {\n      expect(isHtmlInteractiveContent(makeNode('img'), getTextAttrValue)).toBe(false);\n    });\n  });\n\n  describe('<audio> / <video>', () => {\n    it('<audio controls> is interactive', () => {\n      expect(\n        isHtmlInteractiveContent(makeNode('audio', { controls: true }), getTextAttrValue)\n      ).toBe(true);\n    });\n\n    it('<video controls> is interactive', () => {\n      expect(\n        isHtmlInteractiveContent(makeNode('video', { controls: true }), getTextAttrValue)\n      ).toBe(true);\n    });\n\n    it('<audio> without controls is NOT interactive', () => {\n      expect(isHtmlInteractiveContent(makeNode('audio'), getTextAttrValue)).toBe(false);\n    });\n\n    it('<video> without controls is NOT interactive', () => {\n      expect(isHtmlInteractiveContent(makeNode('video'), getTextAttrValue)).toBe(false);\n    });\n  });\n\n  describe('elements NOT in §3.2.5.2.7', () => {\n    // <object> is notably absent from §3.2.5.2.7 — rules that want to flag\n    // <object usemap> nesting (e.g. for upstream ember-template-lint parity)\n    // must do so as a rule-level special case, not via this util.\n    it('<object> is NOT interactive (not in §3.2.5.2.7, even with usemap)', () => {\n      expect(isHtmlInteractiveContent(makeNode('object'), getTextAttrValue)).toBe(false);\n      expect(isHtmlInteractiveContent(makeNode('object', { usemap: '#m' }), getTextAttrValue)).toBe(\n        false\n      );\n    });\n\n    // <area> is not in §3.2.5.2.7 either — rules caring about area[href]\n    // should check via the ARIA widget-role authority (area[href] has\n    // implicit role=link per HTML-AAM).\n    it('<area> is NOT interactive (not in §3.2.5.2.7)', () => {\n      expect(isHtmlInteractiveContent(makeNode('area', { href: '/map' }), getTextAttrValue)).toBe(\n        false\n      );\n    });\n\n    // <canvas>, <option>, <datalist> — ARIA widgets per axobject-query, but\n    // not HTML interactive content. Rules wanting these should consult the\n    // ARIA widget-role authority, not this util.\n    for (const tag of ['canvas', 'option', 'datalist']) {\n      it(`<${tag}> is NOT interactive per HTML §3.2.5.2.7`, () => {\n        expect(isHtmlInteractiveContent(makeNode(tag), getTextAttrValue)).toBe(false);\n      });\n    }\n\n    // Deprecated HTML elements.\n    for (const tag of ['menuitem', 'keygen']) {\n      it(`<${tag}> is NOT interactive (deprecated)`, () => {\n        expect(isHtmlInteractiveContent(makeNode(tag), getTextAttrValue)).toBe(false);\n      });\n    }\n  });\n\n  describe('non-interactive tags', () => {\n    for (const tag of ['div', 'span', 'p', 'section', 'article', 'header', 'footer', 'img']) {\n      it(`<${tag}> is not interactive (no relevant attribute)`, () => {\n        expect(isHtmlInteractiveContent(makeNode(tag), getTextAttrValue)).toBe(false);\n      });\n    }\n  });\n\n  describe('tag normalization', () => {\n    it('lowercases tag names before classification', () => {\n      expect(isHtmlInteractiveContent(makeNode('BUTTON'), getTextAttrValue)).toBe(true);\n      expect(\n        isHtmlInteractiveContent(makeNode('Input', { type: 'hidden' }), getTextAttrValue)\n      ).toBe(false);\n      expect(isHtmlInteractiveContent(makeNode('A', { href: '/x' }), getTextAttrValue)).toBe(true);\n    });\n  });\n\n  describe('edge cases', () => {\n    it('returns false for missing tag', () => {\n      expect(isHtmlInteractiveContent({}, getTextAttrValue)).toBe(false);\n    });\n\n    it('returns false for non-string tag', () => {\n      expect(isHtmlInteractiveContent({ tag: null }, getTextAttrValue)).toBe(false);\n      expect(isHtmlInteractiveContent({ tag: 123 }, getTextAttrValue)).toBe(false);\n    });\n\n    it('returns false for empty-string tag', () => {\n      expect(isHtmlInteractiveContent({ tag: '' }, getTextAttrValue)).toBe(false);\n    });\n\n    it('handles nodes without attributes array (a without href)', () => {\n      expect(isHtmlInteractiveContent({ tag: 'a' }, getTextAttrValue)).toBe(false);\n    });\n\n    it('handles nodes without attributes array (audio/video without controls)', () => {\n      expect(isHtmlInteractiveContent({ tag: 'audio' }, getTextAttrValue)).toBe(false);\n      expect(isHtmlInteractiveContent({ tag: 'video' }, getTextAttrValue)).toBe(false);\n    });\n\n    it('handles nodes without attributes array (img without usemap)', () => {\n      expect(isHtmlInteractiveContent({ tag: 'img' }, getTextAttrValue)).toBe(false);\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/import-test.js",
    "content": "const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst importUtils = require('../../../lib/utils/import');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0].expression;\n}\n\ndescribe('getSourceModuleName', () => {\n  it('gets the correct module name with MemberExpression', () => {\n    const node = parse('DS.Model.extend()').callee;\n    expect(importUtils.getSourceModuleName(node)).toBe('DS');\n  });\n\n  it('gets the correct module name with Identifier', () => {\n    const node = parse('Model.extend()').callee;\n    expect(importUtils.getSourceModuleName(node)).toBe('Model');\n  });\n\n  it('gets the correct module name with CallExpression', () => {\n    const node = parse('Model.extend()');\n    expect(importUtils.getSourceModuleName(node)).toBe('Model');\n  });\n});\n\ndescribe('getImportIdentifier', () => {\n  it('gets null when no import is found', () => {\n    const node = babelESLintParse(\"import { later } from '@ember/runloop';\").body[0];\n    expect(importUtils.getImportIdentifier(node, '@ember/object', 'action')).toBeNull();\n  });\n\n  it('gets an identifier when found', () => {\n    const node = babelESLintParse(\"import { later } from '@ember/runloop';\").body[0];\n    const identifier = importUtils.getImportIdentifier(node, '@ember/runloop', 'later');\n\n    expect(identifier).toBe('later');\n  });\n\n  it('gets an aliased identifier when found', () => {\n    const node = babelESLintParse(\"import { later as laterz } from '@ember/runloop';\").body[0];\n    const identifier = importUtils.getImportIdentifier(node, '@ember/runloop', 'later');\n\n    expect(identifier).toBe('laterz');\n  });\n\n  it('returns undefined when no default import is found', () => {\n    const node = babelESLintParse(\"import { later } from '@ember/runloop';\").body[0];\n    const identifier = importUtils.getImportIdentifier(node, '@ember/runloop');\n\n    expect(identifier).toBeUndefined();\n  });\n\n  it('gets an identifier when found for default imports', () => {\n    const node = babelESLintParse(\"import Component from '@ember/component';\").body[0];\n    const identifier = importUtils.getImportIdentifier(node, '@ember/component');\n\n    expect(identifier).toBe('Component');\n  });\n\n  it('gets an named identifier when found with mixed imports', () => {\n    const node = babelESLintParse(\"import { later } from '@ember/runloop';\").body[0];\n    const identifier = importUtils.getImportIdentifier(node, '@ember/runloop', 'later');\n\n    expect(identifier).toBe('later');\n  });\n\n  it('gets a default identifier when found with mixed imports', () => {\n    const node = babelESLintParse(\"import Component from '@ember/component';\").body[0];\n    const identifier = importUtils.getImportIdentifier(node, '@ember/component');\n\n    expect(identifier).toBe('Component');\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/interactive-roles-test.js",
    "content": "'use strict';\n\nconst {\n  INTERACTIVE_ROLES,\n  COMPOSITE_WIDGET_CHILDREN,\n} = require('../../../lib/utils/interactive-roles');\n\ndescribe('INTERACTIVE_ROLES', () => {\n  describe('canonical widget roles (ARIA 1.2 widget taxonomy)', () => {\n    for (const role of [\n      'button',\n      'checkbox',\n      'combobox',\n      'link',\n      'menuitem',\n      'menuitemcheckbox',\n      'menuitemradio',\n      'option',\n      'radio',\n      'scrollbar',\n      'searchbox',\n      'slider',\n      'spinbutton',\n      'switch',\n      'tab',\n      'textbox',\n      'treeitem',\n    ]) {\n      it(`includes \"${role}\"`, () => {\n        expect(INTERACTIVE_ROLES.has(role)).toBe(true);\n      });\n    }\n  });\n\n  describe('composite widget containers (widget-descended per aria-query)', () => {\n    for (const role of ['listbox', 'menu', 'menubar', 'grid', 'tablist', 'tree', 'treegrid']) {\n      it(`includes \"${role}\"`, () => {\n        expect(INTERACTIVE_ROLES.has(role)).toBe(true);\n      });\n    }\n  });\n\n  describe('manual override', () => {\n    it('includes \"toolbar\" (not widget-descended per aria-query; added per APG convention)', () => {\n      expect(INTERACTIVE_ROLES.has('toolbar')).toBe(true);\n    });\n  });\n\n  describe('manual exclusion', () => {\n    it('does NOT include \"tooltip\" (structure role per WAI-ARIA 1.2 §5.3.3)', () => {\n      expect(INTERACTIVE_ROLES.has('tooltip')).toBe(false);\n    });\n  });\n\n  describe('contested inclusions (documented divergences from peers)', () => {\n    it('includes \"progressbar\" (widget-descended per aria-query; diverges from lit-a11y which excludes it as readonly-valued)', () => {\n      expect(INTERACTIVE_ROLES.has('progressbar')).toBe(true);\n    });\n  });\n\n  describe('non-widget roles excluded', () => {\n    // Spot-check a handful of roles that should NOT be in the interactive set\n    // because they're abstract, structural, or landmark-typed.\n    for (const role of [\n      'article', // document-structure\n      'banner', // landmark\n      'main', // landmark\n      'navigation', // landmark\n      'region', // landmark\n      'complementary', // landmark\n      'contentinfo', // landmark\n      'form', // landmark\n      'search', // landmark (role, not element)\n      'heading', // document-structure\n      'img', // document-structure\n      'list', // document-structure\n      'listitem', // document-structure\n      'paragraph', // document-structure\n      'separator', // document-structure (context-dependent; aria-query taxonomy says structure)\n      'presentation', // role\n      'none', // role\n      'widget', // abstract — excluded by filter\n      'structure', // abstract — excluded by filter\n      'window', // abstract — excluded by filter\n    ]) {\n      it(`does NOT include \"${role}\"`, () => {\n        expect(INTERACTIVE_ROLES.has(role)).toBe(false);\n      });\n    }\n  });\n\n  describe('set size (drift detection)', () => {\n    // Pins the current set size to surface aria-query's taxonomy changes as\n    // visible diffs rather than silent shifts. Update this number (with a\n    // commit message naming which role was added/removed) when aria-query\n    // is bumped.\n    it('has 35 roles', () => {\n      expect(INTERACTIVE_ROLES.size).toBe(35);\n    });\n  });\n});\n\ndescribe('COMPOSITE_WIDGET_CHILDREN', () => {\n  it('is a Map', () => {\n    expect(COMPOSITE_WIDGET_CHILDREN).toBeInstanceOf(Map);\n  });\n\n  it('maps \"listbox\" to include \"option\"', () => {\n    expect(COMPOSITE_WIDGET_CHILDREN.get('listbox')?.has('option')).toBe(true);\n  });\n\n  it('maps \"tablist\" to include \"tab\"', () => {\n    expect(COMPOSITE_WIDGET_CHILDREN.get('tablist')?.has('tab')).toBe(true);\n  });\n\n  it('maps \"tree\" to include \"treeitem\"', () => {\n    expect(COMPOSITE_WIDGET_CHILDREN.get('tree')?.has('treeitem')).toBe(true);\n  });\n\n  it('maps \"grid\" to include \"row\" and transitively \"gridcell\"', () => {\n    const gridChildren = COMPOSITE_WIDGET_CHILDREN.get('grid');\n    expect(gridChildren?.has('row')).toBe(true);\n    expect(gridChildren?.has('gridcell')).toBe(true);\n  });\n\n  it('maps \"treegrid\" to include both grid-row and tree-treeitem (superClass inheritance)', () => {\n    const treeGridChildren = COMPOSITE_WIDGET_CHILDREN.get('treegrid');\n    expect(treeGridChildren?.has('row')).toBe(true);\n    expect(treeGridChildren?.has('treeitem')).toBe(true);\n  });\n\n  it('maps \"radiogroup\" to include \"radio\"', () => {\n    expect(COMPOSITE_WIDGET_CHILDREN.get('radiogroup')?.has('radio')).toBe(true);\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/is-native-element-test.js",
    "content": "'use strict';\n\nconst { isNativeElement, ELEMENT_TAGS } = require('../../../lib/utils/is-native-element');\n\n// Tests exercise the list-lookup path only. Scope-based shadowing is covered\n// by the rule-level test suites (see tests/lib/rules/template-no-block-params-\n// for-html-elements.js and siblings) because it requires a real ESLint\n// SourceCode / scope manager that's only built up by the rule tester.\n\ndescribe('isNativeElement — list-only behavior (no sourceCode)', () => {\n  it('returns true for lowercase HTML tag names', () => {\n    expect(isNativeElement({ tag: 'div' })).toBe(true);\n    expect(isNativeElement({ tag: 'article' })).toBe(true);\n    expect(isNativeElement({ tag: 'h1' })).toBe(true);\n    expect(isNativeElement({ tag: 'button' })).toBe(true);\n    expect(isNativeElement({ tag: 'form' })).toBe(true);\n    expect(isNativeElement({ tag: 'section' })).toBe(true);\n  });\n\n  it('returns true for SVG tag names', () => {\n    expect(isNativeElement({ tag: 'svg' })).toBe(true);\n    expect(isNativeElement({ tag: 'circle' })).toBe(true);\n    expect(isNativeElement({ tag: 'path' })).toBe(true);\n  });\n\n  it('returns true for MathML tag names', () => {\n    expect(isNativeElement({ tag: 'mfrac' })).toBe(true);\n    expect(isNativeElement({ tag: 'math' })).toBe(true);\n  });\n\n  it('returns false for PascalCase component tags', () => {\n    expect(isNativeElement({ tag: 'Button' })).toBe(false);\n    expect(isNativeElement({ tag: 'MyWidget' })).toBe(false);\n    // Native-tag names in PascalCase — the core bug case.\n    expect(isNativeElement({ tag: 'Article' })).toBe(false);\n    expect(isNativeElement({ tag: 'Form' })).toBe(false);\n    expect(isNativeElement({ tag: 'Main' })).toBe(false);\n    expect(isNativeElement({ tag: 'Nav' })).toBe(false);\n    expect(isNativeElement({ tag: 'Section' })).toBe(false);\n    expect(isNativeElement({ tag: 'Table' })).toBe(false);\n  });\n\n  it('returns false for named-arg invocations', () => {\n    expect(isNativeElement({ tag: '@heading' })).toBe(false);\n    expect(isNativeElement({ tag: '@tag.foo' })).toBe(false);\n  });\n\n  it('returns false for this-path invocations', () => {\n    expect(isNativeElement({ tag: 'this.myComponent' })).toBe(false);\n    expect(isNativeElement({ tag: 'this.comp.sub' })).toBe(false);\n  });\n\n  it('returns false for dot-path invocations', () => {\n    expect(isNativeElement({ tag: 'foo.bar' })).toBe(false);\n    expect(isNativeElement({ tag: 'ns.widget' })).toBe(false);\n  });\n\n  it('returns false for named-block / namespaced invocations', () => {\n    expect(isNativeElement({ tag: 'foo::bar' })).toBe(false);\n    expect(isNativeElement({ tag: 'Foo::Bar' })).toBe(false);\n  });\n\n  it('returns false for custom elements (accepted false negative)', () => {\n    // Custom elements aren't in the html-tags/svg-tags/mathml-tag-names\n    // allowlists. They're treated as \"not a native element\" so downstream\n    // rules skip them — matches the convention established in PR #2689.\n    expect(isNativeElement({ tag: 'my-element' })).toBe(false);\n    expect(isNativeElement({ tag: 'x-foo' })).toBe(false);\n  });\n\n  it('returns false for empty / missing / non-string tag', () => {\n    expect(isNativeElement({ tag: '' })).toBe(false);\n    expect(isNativeElement({ tag: undefined })).toBe(false);\n    expect(isNativeElement({ tag: null })).toBe(false);\n    expect(isNativeElement({ tag: 123 })).toBe(false);\n    expect(isNativeElement({})).toBe(false);\n    expect(isNativeElement()).toBe(false);\n    expect(isNativeElement(null)).toBe(false);\n  });\n});\n\ndescribe('ELEMENT_TAGS', () => {\n  it('includes all HTML, SVG, and MathML tag names', () => {\n    // Sanity check — if this ever drops below a reasonable size, one of the\n    // underlying packages has changed contract.\n    expect(ELEMENT_TAGS.size).toBeGreaterThan(200);\n    expect(ELEMENT_TAGS.has('div')).toBe(true);\n    expect(ELEMENT_TAGS.has('circle')).toBe(true);\n    expect(ELEMENT_TAGS.has('mfrac')).toBe(true);\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/javascript-test.js",
    "content": "'use strict';\n\nconst javascriptUtils = require('../../../lib/utils/javascript');\n\ndescribe('duplicateArrays', () => {\n  it('returns the right result', () => {\n    expect(javascriptUtils.duplicateArrays([], 2)).toStrictEqual([]);\n    expect(javascriptUtils.duplicateArrays(['abc'], 2)).toStrictEqual(['abc', 'abc', 'abc']);\n    expect(javascriptUtils.duplicateArrays(['first', 'second'], 2)).toStrictEqual([\n      'first',\n      'second',\n      'first',\n      'second',\n      'first',\n      'second',\n    ]);\n  });\n});\n\ndescribe('removeWhitespace', () => {\n  it('returns the right result', () => {\n    expect(javascriptUtils.removeWhitespace('abcdef')).toBe('abcdef');\n    expect(javascriptUtils.removeWhitespace('abc def')).toBe('abcdef');\n    expect(javascriptUtils.removeWhitespace(' abc def ')).toBe('abcdef');\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/landmark-roles-test.js",
    "content": "'use strict';\n\nconst { LANDMARK_ROLES, ALL_LANDMARK_ROLES } = require('../../../lib/utils/landmark-roles');\n\n// The WAI-ARIA 1.2 §5.3.4 landmark roles known at authoring time. Asserting\n// set-inclusion (not an exact-equality / size match) keeps the test robust\n// to future aria-query updates that add non-abstract landmark roles — the\n// derivation (non-abstract + superClass contains 'landmark' + not doc-*)\n// is what we actually care about.\nconst KNOWN_LANDMARK_ROLES = [\n  'banner',\n  'complementary',\n  'contentinfo',\n  'form',\n  'main',\n  'navigation',\n  'region',\n  'search',\n];\n\ndescribe('ALL_LANDMARK_ROLES', () => {\n  it('includes every WAI-ARIA 1.2 §5.3.4 landmark role', () => {\n    for (const role of KNOWN_LANDMARK_ROLES) {\n      expect(ALL_LANDMARK_ROLES.has(role)).toBe(true);\n    }\n    // Floor, not ceiling — lets the set grow if aria-query adds new\n    // non-abstract landmark roles upstream.\n    expect(ALL_LANDMARK_ROLES.size).toBeGreaterThanOrEqual(KNOWN_LANDMARK_ROLES.length);\n  });\n\n  it('excludes DPub-ARIA doc-* roles', () => {\n    for (const role of ALL_LANDMARK_ROLES) {\n      expect(role.startsWith('doc-')).toBe(false);\n    }\n  });\n});\n\ndescribe('LANDMARK_ROLES (the statically-verifiable subset)', () => {\n  it('includes every known landmark role except region', () => {\n    for (const role of KNOWN_LANDMARK_ROLES) {\n      if (role === 'region') {\n        continue;\n      }\n      expect(LANDMARK_ROLES.has(role)).toBe(true);\n    }\n    // Floor, not ceiling — mirrors the ALL_LANDMARK_ROLES rationale, minus\n    // the deliberate region exclusion.\n    expect(LANDMARK_ROLES.size).toBeGreaterThanOrEqual(KNOWN_LANDMARK_ROLES.length - 1);\n  });\n\n  it('excludes region (cannot verify accessible-name presence statically)', () => {\n    expect(LANDMARK_ROLES.has('region')).toBe(false);\n    expect(ALL_LANDMARK_ROLES.has('region')).toBe(true);\n  });\n\n  it('excludes non-landmark roles (button, link, article)', () => {\n    expect(LANDMARK_ROLES.has('button')).toBe(false);\n    expect(LANDMARK_ROLES.has('link')).toBe(false);\n    expect(LANDMARK_ROLES.has('article')).toBe(false);\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/new-module-test.js",
    "content": "'use strict';\n\nconst { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst { buildFix, isDestructuring } = require('../../../lib/utils/new-module');\n\nconst modulesData = {\n  'ember-data/model': {\n    default: ['@ember-data/model'],\n  },\n};\n\ndescribe('isDestructuring', () => {\n  it('should check a destructuring import', () => {\n    const node = babelESLintParse('const { destructured } = someVar;').body[0].declarations[0];\n    expect(isDestructuring(node)).toBeTruthy();\n  });\n\n  it('should check a non-destructuring import', () => {\n    const node = babelESLintParse('const destructured = someVar;').body[0].declarations[0];\n    expect(isDestructuring(node)).toBeFalsy();\n  });\n\n  it('should check a ForInStatement', () => {\n    const node = babelESLintParse('for (const item in list) {};').body[0].left.declarations[0];\n    expect(isDestructuring(node)).toBeFalsy();\n  });\n});\n\ndescribe('buildFix', () => {\n  it('returns a function', () => {\n    const node = babelESLintParse('import Model from \"ember-data/model\"').body[0];\n    const fix = buildFix(node, modulesData);\n    expect(typeof fix === 'function').toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/property-getter-test.js",
    "content": "'use strict';\n\nconst { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst propertyGetterUtils = require('../../../lib/utils/property-getter');\nconst { FauxContext } = require('../../helpers/faux-context');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0].expression;\n}\n\ndescribe('isSimpleThisExpression', () => {\n  it('behaves correctly', () => {\n    // False:\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x().y'))).toBeFalsy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x()'))).toBeFalsy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x.y()'))).toBeFalsy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x[1]'))).toBeFalsy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x[i]'))).toBeFalsy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x.y[i]'))).toBeFalsy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this?.get()'))).toBeFalsy();\n\n    // True:\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x'))).toBeTruthy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x.y'))).toBeTruthy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x?.y'))).toBeTruthy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.x?.y?.z'))).toBeTruthy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.get(\"property\")'))).toBeTruthy();\n    expect(propertyGetterUtils.isSimpleThisExpression(parse('this.get(\"x.y\")'))).toBeTruthy();\n  });\n});\n\ndescribe('isThisGetCall', () => {\n  it('behaves correctly', () => {\n    // False:\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x.y'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x().y'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x()'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x.y()'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x[1]'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x[i]'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.x.y[i]'))).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.get(\"property\").something'))).toBeFalsy();\n    expect(\n      propertyGetterUtils.isThisGetCall(parse('this.get(\"unexpected\", \"argument\").something'))\n    ).toBeFalsy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this?.get(\"property\")'))).toBeFalsy();\n\n    // True:\n    expect(propertyGetterUtils.isThisGetCall(parse('this.get(\"property\")'))).toBeTruthy();\n    expect(propertyGetterUtils.isThisGetCall(parse('this.get(\"x.y\")'))).toBeTruthy();\n  });\n});\n\ndescribe('nodeToDependentKey', () => {\n  it('behaves correctly', () => {\n    let context = new FauxContext('this.x');\n    let node = context.ast.body[0];\n    expect(propertyGetterUtils.nodeToDependentKey(node, context)).toBe('x');\n\n    context = new FauxContext('this.x.y');\n    node = context.ast.body[0];\n    expect(propertyGetterUtils.nodeToDependentKey(node, context)).toBe('x.y');\n\n    context = new FauxContext('this.get(\"x\")');\n    node = context.ast.body[0].expression;\n    expect(propertyGetterUtils.nodeToDependentKey(node, context)).toBe('x');\n\n    context = new FauxContext('this.x()');\n    node = context.ast.body[0].expression;\n    expect(propertyGetterUtils.nodeToDependentKey(node, context)).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/property-order-test.js",
    "content": "'use-strict';\n\nconst propertyOrder = require('../../../lib/utils/property-order');\nconst { FauxContext } = require('../../helpers/faux-context');\n\ndescribe('addBackwardsPosition', () => {\n  it('should not change the order if the desired position is already included in the order', () => {\n    const newOrder = propertyOrder.addBackwardsPosition(\n      ['method', 'empty-method'],\n      'empty-method',\n      'method'\n    );\n    expect(newOrder).toStrictEqual(['method', 'empty-method']);\n  });\n\n  it('should not change the order if the desired position is already included as part of another position group', () => {\n    const newOrder = propertyOrder.addBackwardsPosition(\n      [['method', 'empty-method'], 'foo'],\n      'empty-method',\n      'method'\n    );\n    expect(newOrder).toStrictEqual([['method', 'empty-method'], 'foo']);\n  });\n\n  it('should not add the position, if the target position is not present', () => {\n    const newOrder = propertyOrder.addBackwardsPosition(['foo'], 'empty-method', 'bar');\n    expect(newOrder).toStrictEqual(['foo']);\n  });\n\n  it('should add the desired position to the existing target position when the target position is on its own position', () => {\n    const newOrder = propertyOrder.addBackwardsPosition(\n      ['method', 'foo'],\n      'empty-method',\n      'method'\n    );\n    expect(newOrder).toStrictEqual([['method', 'empty-method'], 'foo']);\n  });\n\n  it('should add the desired position to the existing target position when the target position is part of a group', () => {\n    const newOrder = propertyOrder.addBackwardsPosition(\n      [['method', 'bar'], 'foo'],\n      'empty-method',\n      'method'\n    );\n    expect(newOrder).toStrictEqual([['method', 'bar', 'empty-method'], 'foo']);\n  });\n});\n\ndescribe('determinePropertyType', () => {\n  describe('classic classes', () => {\n    it('should determine service-type props', () => {\n      const context = new FauxContext(\n        `import {inject as service} from '@ember/service';\n        export default Controller.extend({\n          currentUser: service(),\n        });`\n      );\n      const importInjectName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(\n        propertyOrder.determinePropertyType(node, 'controller', [], undefined, importInjectName)\n      ).toBe('service');\n    });\n\n    it('should determine controller-type props with full import', () => {\n      const context = new FauxContext(\n        `import Ember from 'ember';\n        export default Controller.extend({\n          application: Ember.inject.controller(),\n        });`\n      );\n      const importEmberName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller', [], importEmberName)).toBe(\n        'controller'\n      );\n    });\n\n    it('should determine controller-type props', () => {\n      const context = new FauxContext(\n        `import {inject as controller} from '@ember/controller';\n        export default Controller.extend({\n          application: controller(),\n        });`\n      );\n      const importControllerName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(\n        propertyOrder.determinePropertyType(\n          node,\n          'controller',\n          [],\n          undefined,\n          undefined,\n          undefined,\n          importControllerName\n        )\n      ).toBe('controller');\n    });\n\n    it('should determine init-type props', () => {\n      const context = new FauxContext(\n        `export default Controller.extend({\n          init() {}\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller')).toBe('init');\n    });\n\n    it('should determine component lifecycle hooks', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          didInsertElement() {}\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component')).toBe('didInsertElement');\n    });\n\n    it('should determine query-params', () => {\n      const context = new FauxContext(\n        `export default Controller.extend({\n          queryParams: []\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller')).toBe('query-params');\n    });\n\n    it('should determine inherited properties', () => {\n      const context = new FauxContext(\n        `export default Controller.extend({\n          isDestroyed: false\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller')).toBe('inherited-property');\n    });\n\n    it('should determine attributes', () => {\n      const context = new FauxContext(\n        `export default Model.extend({\n          someAttr: DS.attr()\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'model')).toBe('attribute');\n    });\n\n    it('should determine relationships', () => {\n      const context = new FauxContext(\n        `export default Model.extend({\n          someRelationship: hasMany('otherModel')\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'model')).toBe('relationship');\n    });\n\n    it('should determine observer-type props with full import', () => {\n      const context = new FauxContext(\n        `import Ember from 'ember';\n        export default Controller.extend({\n          someObvs: Ember.observer(),\n        });`\n      );\n      const importEmberName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller', [], importEmberName)).toBe(\n        'observer'\n      );\n    });\n\n    it('should determine observer-type props', () => {\n      const context = new FauxContext(\n        `import {observer} from '@ember/object';\n        export default Controller.extend({\n          someObvs: observer(),\n        });`\n      );\n      const importObserverName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].declaration.arguments[0].properties[0];\n      expect(\n        propertyOrder.determinePropertyType(\n          node,\n          'controller',\n          [],\n          undefined,\n          undefined,\n          importObserverName\n        )\n      ).toBe('observer');\n    });\n\n    it('should determine actions', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          actions: {}\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component')).toBe('actions');\n    });\n\n    it('should determine single-line functions', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          foo: boo()\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component')).toBe('single-line-function');\n    });\n\n    it('should determine multi-line functions', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          foo: boo(bar, baz, () => {\n            console.log('boop')\n          })\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component')).toBe('multi-line-function');\n    });\n\n    it('should determine properties', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          foo: \"boo\"\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe('property');\n    });\n\n    it('should determine template literals as properties', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          foo: ${`foo${'bar'}`}\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe('property');\n    });\n\n    it('should determine spread syntax as a spread property', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          ...foo\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe('spread');\n    });\n\n    it('should determine empty methods', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          foo() {}\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe('empty-method');\n    });\n\n    it('should determine methods', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          foo() { console.log(\"hello\") }\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe('method');\n    });\n\n    it('should determine custom properties when given order with custom property', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          myFooProperty: null\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(\n        propertyOrder.determinePropertyType(node, 'component', [\n          'custom:myBarProperty',\n          'custom:myFooProperty',\n        ])\n      ).toBe('custom:myFooProperty');\n    });\n\n    it('should determine custom properties as normal properties when given order without custom property', () => {\n      const context = new FauxContext(\n        `export default Component.extend({\n          myFooProperty: null\n        });`\n      );\n      const node = context.ast.body[0].declaration.arguments[0].properties[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', ['custom:myBarProperty'])).toBe(\n        'property'\n      );\n    });\n  });\n\n  describe('native classes', () => {\n    it('should determine service-type props', () => {\n      const context = new FauxContext(\n        `import {inject as service} from '@ember/service';\n        class MyController extends Controller {\n          @service currentUser;\n        }`\n      );\n      const importInjectName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(\n        propertyOrder.determinePropertyType(node, 'controller', [], undefined, importInjectName)\n      ).toBe('service');\n    });\n\n    it('should determine controller-type props', () => {\n      const context = new FauxContext(\n        `import {inject as controller} from '@ember/controller';\n        class MyController extends Controller {\n          @controller application;\n        }`\n      );\n      const importControllerName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(\n        propertyOrder.determinePropertyType(\n          node,\n          'controller',\n          [],\n          undefined,\n          undefined,\n          undefined,\n          importControllerName\n        )\n      ).toBe('controller');\n    });\n\n    it('should determine init-type props', () => {\n      const context = new FauxContext(\n        `class MyController extends Controller {\n          init() {}\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller')).toBe('init');\n    });\n\n    it('should determine query-params', () => {\n      const context = new FauxContext(\n        `class MyController extends Controller {\n          queryParams = [];\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'controller')).toBe('query-params');\n    });\n\n    it('should determine attributes', () => {\n      const context = new FauxContext(\n        `class MyModel extends Model {\n          @attr someAttr;\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'model')).toBe('attribute');\n    });\n\n    it('should determine relationships', () => {\n      const context = new FauxContext(\n        `class MyModel extends Model {\n          @hasMany('otherModel') someRelationship;\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'model')).toBe('relationship');\n    });\n\n    it('should determine observer-type props', () => {\n      const context = new FauxContext(\n        `import {observer} from '@ember/object';\n        class MyController extends Controller {\n          @observer someObvs;\n        }`\n      );\n      const importObserverName = context.ast.body[0].specifiers[0].local.name;\n      const node = context.ast.body[1].body.body[0];\n      expect(\n        propertyOrder.determinePropertyType(\n          node,\n          'controller',\n          [],\n          undefined,\n          undefined,\n          importObserverName\n        )\n      ).toBe('observer');\n    });\n\n    it('should determine single-line functions', () => {\n      const context = new FauxContext(\n        `class MyComponent extends Component {\n          foo() {}\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'component')).toBe('single-line-function');\n    });\n\n    it('should determine multi-line functions', () => {\n      const context = new FauxContext(\n        `class MyComponent extends Component {\n          foo(bar) {\n            console.log(bar)\n          }\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe(\n        'multi-line-function'\n      );\n    });\n\n    it('should determine properties', () => {\n      const context = new FauxContext(\n        `class MyComponent extends Component {\n          foo = \"boo\";\n        }`\n      );\n      const node = context.ast.body[0].body.body[0];\n      expect(propertyOrder.determinePropertyType(node, 'component', [])).toBe('property');\n    });\n  });\n});\n\ndescribe('reportUnorderedProperties', () => {\n  describe('classic classes', () => {\n    it('should not report nodes if the order is correct', () => {\n      const order = ['controller', 'service', 'query-params'];\n      const context = new FauxContext(\n        `import {inject as service} from '@ember/service';\n        import {inject as controller} from '@ember/controller';\n        export default Controller.extend({\n          application: controller(),\n          currentUser: service(),\n          queryParams: [],\n        });`,\n        '',\n        vi.fn()\n      );\n      const importInjectName = context.ast.body[0].specifiers[0].local.name;\n      const importControllerName = context.ast.body[1].specifiers[0].local.name;\n      const node = context.ast.body[2].declaration;\n\n      propertyOrder.reportUnorderedProperties(\n        node,\n        context,\n        'controller',\n        order,\n        undefined,\n        importInjectName,\n        undefined,\n        importControllerName\n      );\n      expect(context.report).not.toHaveBeenCalled();\n    });\n\n    it('should report nodes if the order is incorrect', () => {\n      const order = ['controller', 'service', 'query-params'];\n      const context = new FauxContext(\n        `import {inject as service} from '@ember/service';\n        import {inject as controller} from '@ember/controller';\n        export default Controller.extend({\n            currentUser: service(),\n            application: controller(),\n            queryParams: [],\n          });`,\n        '',\n        vi.fn()\n      );\n      const importInjectName = context.ast.body[0].specifiers[0].local.name;\n      const importControllerName = context.ast.body[1].specifiers[0].local.name;\n      const node = context.ast.body[2].declaration;\n\n      propertyOrder.reportUnorderedProperties(\n        node,\n        context,\n        'controller',\n        order,\n        undefined,\n        importInjectName,\n        undefined,\n        importControllerName\n      );\n      expect(context.report).toHaveBeenCalled();\n    });\n  });\n\n  describe('native classes', () => {\n    it('should not report nodes if the order is correct', () => {\n      const order = ['controller', 'service', 'query-params'];\n      const context = new FauxContext(\n        `import {inject as service} from '@ember/service';\n        import {inject as controller} from '@ember/controller';\n        export default class MyController extends Controller {\n            @controller application;\n            @service currentUser;\n            queryParams = [];\n          }`,\n        '',\n        vi.fn()\n      );\n      const importInjectName = context.ast.body[0].specifiers[0].local.name;\n      const importControllerName = context.ast.body[1].specifiers[0].local.name;\n      const node = context.ast.body[2].declaration;\n\n      propertyOrder.reportUnorderedProperties(\n        node,\n        context,\n        'controller',\n        order,\n        undefined,\n        importInjectName,\n        undefined,\n        importControllerName\n      );\n      expect(context.report).not.toHaveBeenCalled();\n    });\n\n    it('should report nodes if the order is incorrect', () => {\n      const order = ['controller', 'service', 'query-params'];\n      const context = new FauxContext(\n        `import {inject as service} from '@ember/service';\n        import {inject as controller} from '@ember/controller';\n        export default class MyController extends Controller {\n            @service currentUser;\n            @controller application;\n            queryParams = [];\n          }`,\n        '',\n        vi.fn()\n      );\n      const importInjectName = context.ast.body[0].specifiers[0].local.name;\n      const importControllerName = context.ast.body[1].specifiers[0].local.name;\n      const node = context.ast.body[2].declaration;\n\n      propertyOrder.reportUnorderedProperties(\n        node,\n        context,\n        'controller',\n        order,\n        undefined,\n        importInjectName,\n        undefined,\n        importControllerName\n      );\n      expect(context.report).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/property-setter-test.js",
    "content": "'use strict';\n\nconst { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst propertySetterUtils = require('../../../lib/utils/property-setter');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0];\n}\n\ndescribe('isThisSet', () => {\n  it('behaves correctly', () => {\n    // False:\n    expect(propertySetterUtils.isThisSet(parse('this.x').expression)).toBeFalsy();\n    expect(propertySetterUtils.isThisSet(parse('this.x()').expression)).toBeFalsy();\n    expect(propertySetterUtils.isThisSet(parse('let x = 123;'))).toBeFalsy();\n    expect(propertySetterUtils.isThisSet(parse('x = 123;'))).toBeFalsy();\n\n    // True:\n    expect(propertySetterUtils.isThisSet(parse('this.x = 123').expression)).toBeTruthy();\n    expect(propertySetterUtils.isThisSet(parse('this.x.y = 123').expression)).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/scope-references-this-test.js",
    "content": "'use strict';\n\nconst { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst scopeReferencesThis = require('../../../lib/utils/scope-references-this');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0];\n}\n\ndescribe('scopeReferencesThis', function () {\n  it('recognizes simple cases`', function () {\n    expect(scopeReferencesThis(parse('this'))).toBeTruthy(); // `this` uses `this`\n    expect(scopeReferencesThis(parse('\"this\"'))).toBeFalsy(); // the string \"this\" does not use this\n    expect(scopeReferencesThis(parse('class Foo { @someDecorator() someProp }'))).toBeFalsy(); // Does not throw with node type (ClassProperty) not handled by estraverse.\n  });\n\n  it('can find nested `this`', function () {\n    expect(scopeReferencesThis(parse('if (a) { this } else { null }'))).toBeTruthy(); // if statement uses this\n    expect(scopeReferencesThis(parse('() => this'))).toBeTruthy(); // arrow function uses outer this\n  });\n\n  it('does not consider `this` within non-arrow functions', function () {\n    expect(scopeReferencesThis(parse('function foo() { return this; }'))).toBeFalsy(); // function uses different this\n    expect(scopeReferencesThis(parse('function foo() { return () => this; }'))).toBeFalsy(); // inner arrow function uses different this'\n    expect(scopeReferencesThis(parse('() => function() { return this; }'))).toBeFalsy(); // inner function uses different this\n    expect(scopeReferencesThis(parse('({ a() { this } })'))).toBeFalsy(); // object method uses different this\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/static-attr-value-test.js",
    "content": "'use strict';\n\nconst { getStaticAttrValue } = require('../../../lib/utils/static-attr-value');\n\ndescribe('getStaticAttrValue', () => {\n  it('returns empty string for null/undefined (valueless attribute)', () => {\n    expect(getStaticAttrValue(null)).toBe('');\n    expect(getStaticAttrValue(undefined)).toBe('');\n  });\n\n  it('returns chars for GlimmerTextNode', () => {\n    expect(getStaticAttrValue({ type: 'GlimmerTextNode', chars: 'hello' })).toBe('hello');\n    expect(getStaticAttrValue({ type: 'GlimmerTextNode', chars: '' })).toBe('');\n  });\n\n  it('unwraps GlimmerMustacheStatement with BooleanLiteral', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerBooleanLiteral', value: true },\n      })\n    ).toBe('true');\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerBooleanLiteral', value: false },\n      })\n    ).toBe('false');\n  });\n\n  it('unwraps GlimmerMustacheStatement with StringLiteral', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerStringLiteral', value: 'foo' },\n      })\n    ).toBe('foo');\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerStringLiteral', value: '' },\n      })\n    ).toBe('');\n  });\n\n  it('unwraps GlimmerMustacheStatement with NumberLiteral', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerNumberLiteral', value: -1 },\n      })\n    ).toBe('-1');\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerNumberLiteral', value: 0 },\n      })\n    ).toBe('0');\n  });\n\n  it('returns undefined for GlimmerMustacheStatement with a dynamic PathExpression', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerMustacheStatement',\n        path: { type: 'GlimmerPathExpression', original: 'this.foo' },\n      })\n    ).toBeUndefined();\n  });\n\n  it('joins GlimmerConcatStatement with only static parts', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerConcatStatement',\n        parts: [\n          { type: 'GlimmerTextNode', chars: 'prefix-' },\n          {\n            type: 'GlimmerMustacheStatement',\n            path: { type: 'GlimmerStringLiteral', value: 'mid' },\n          },\n          { type: 'GlimmerTextNode', chars: '-suffix' },\n        ],\n      })\n    ).toBe('prefix-mid-suffix');\n  });\n\n  it('joins concat with boolean and number literal parts', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerConcatStatement',\n        parts: [\n          {\n            type: 'GlimmerMustacheStatement',\n            path: { type: 'GlimmerBooleanLiteral', value: true },\n          },\n        ],\n      })\n    ).toBe('true');\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerConcatStatement',\n        parts: [\n          {\n            type: 'GlimmerMustacheStatement',\n            path: { type: 'GlimmerNumberLiteral', value: -1 },\n          },\n        ],\n      })\n    ).toBe('-1');\n  });\n\n  it('returns undefined for GlimmerConcatStatement with a dynamic part', () => {\n    expect(\n      getStaticAttrValue({\n        type: 'GlimmerConcatStatement',\n        parts: [\n          { type: 'GlimmerTextNode', chars: 'x-' },\n          {\n            type: 'GlimmerMustacheStatement',\n            path: { type: 'GlimmerPathExpression', original: 'this.foo' },\n          },\n        ],\n      })\n    ).toBeUndefined();\n  });\n\n  it('returns undefined for an unknown node type', () => {\n    expect(getStaticAttrValue({ type: 'GlimmerSubExpression' })).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/types-test.js",
    "content": "const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst types = require('../../../lib/utils/types');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0].expression;\n}\n\ndescribe('function sort order', function () {\n  it('has exported functions in sorted order', function () {\n    expect(Object.getOwnPropertyNames(types)).toStrictEqual(\n      Object.getOwnPropertyNames(types).sort()\n    );\n  });\n});\n\ndescribe('isArrayExpression', () => {\n  const node = parse('test = []').right;\n\n  it('should check if node is array expression', () => {\n    expect(types.isArrayExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isArrowFunctionExpression', () => {\n  const node = parse('test = () => {}').right;\n\n  it('should check if node is arrow function expression', () => {\n    expect(types.isArrowFunctionExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isCallExpression', () => {\n  const node = parse('test()');\n\n  it('should check if node is call expression', () => {\n    expect(types.isCallExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isCallWithFunctionExpression', () => {\n  const node = parse('mysteriousFnc(function(){})');\n\n  it('should check if node is call with function expression', () => {\n    expect(types.isCallWithFunctionExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isConciseArrowFunctionExpressionWithCall', () => {\n  const node = parse('test = () => foo()').right;\n  const blockNode = parse('test = () => { foo() }').right;\n\n  it('should check if node is concise arrow function expression with call expression body', () => {\n    expect(types.isConciseArrowFunctionWithCallExpression(node)).toBeTruthy();\n  });\n\n  it('should check if node does not have block body', () => {\n    expect(!types.isConciseArrowFunctionWithCallExpression(blockNode)).toBeTruthy();\n  });\n});\n\ndescribe('isConditionalExpression', () => {\n  const node = parse(\"test = true ? 'asd' : 'qwe'\").right;\n\n  it('should check if node is a conditional expression', () => {\n    expect(types.isConditionalExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isFunctionExpression', () => {\n  const node = parse('test = function () {}').right;\n\n  it('should check if node is function expression', () => {\n    expect(types.isFunctionExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isIdentifier', () => {\n  const node = parse('test');\n\n  it('should check if node is identifier', () => {\n    expect(types.isIdentifier(node)).toBeTruthy();\n  });\n});\n\ndescribe('isLiteral', () => {\n  const node = parse('\"test\"');\n\n  it('should check if node is identifier', () => {\n    expect(types.isLiteral(node)).toBeTruthy();\n  });\n});\n\ndescribe('isMemberExpression', () => {\n  const node = parse('test.value');\n\n  it('should check if node is member expression', () => {\n    expect(types.isMemberExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isNewExpression', () => {\n  const node = parse('new Date()');\n\n  it('should check if node is new expression', () => {\n    expect(types.isNewExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isObjectExpression', () => {\n  const node = parse('test = {}').right;\n\n  it('should check if node is identifier', () => {\n    expect(types.isObjectExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isReturnStatement', () => {\n  const node = babelESLintParse('return').body[0];\n\n  it('should check if node is a return statement', () => {\n    expect(types.isReturnStatement(node)).toBeTruthy();\n  });\n});\n\ndescribe('isString', () => {\n  it('recognizes template literals', () => {\n    const node = parse('`template literal`');\n    expect(types.isString(node)).toBeTruthy();\n  });\n\n  it('recognizes template literals with interpolation', () => {\n    const node = parse('`template ${123} literal`'); // eslint-disable-line no-template-curly-in-string\n    expect(types.isString(node)).toBeTruthy();\n  });\n\n  it('recognizes string literals', () => {\n    const node = parse(\"'string literal'\");\n    expect(types.isString(node)).toBeTruthy();\n  });\n\n  it('ignores identifiers', () => {\n    const node = parse('MY_VARIABLE');\n    expect(types.isString(node)).not.toBeTruthy();\n  });\n\n  it('ignores number literals', () => {\n    const node = parse('123');\n    expect(types.isString(node)).not.toBeTruthy();\n  });\n});\n\ndescribe('isStringLiteral', () => {\n  it('recognizes string literals', () => {\n    const node = parse(\"'string literal'\");\n    expect(types.isStringLiteral(node)).toBeTruthy();\n  });\n\n  it('ignores template literals', () => {\n    const node = parse('`template literal`');\n    expect(types.isStringLiteral(node)).not.toBeTruthy();\n  });\n\n  it('ignores identifiers', () => {\n    const node = parse('MY_VARIABLE');\n    expect(types.isStringLiteral(node)).not.toBeTruthy();\n  });\n\n  it('ignores number literals', () => {\n    const node = parse('123');\n    expect(types.isStringLiteral(node)).not.toBeTruthy();\n  });\n});\n\ndescribe('isTaggedTemplateExpression', () => {\n  const node = parse('test = hbs`lorem ipsum`;').right;\n\n  it('should check if node is a tagged template expression', () => {\n    expect(types.isTaggedTemplateExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isThisExpression', () => {\n  const node = parse('this');\n\n  it('should check if node is \"this\" expression', () => {\n    expect(types.isThisExpression(node)).toBeTruthy();\n  });\n});\n\ndescribe('isUnaryExpression', () => {\n  const node = parse('-1');\n\n  it('should check if node is identifier', () => {\n    expect(types.isUnaryExpression(node)).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/utils/get-source-module-name-for-identifier-test.js",
    "content": "const { getSourceModuleNameForIdentifier } = require('../../../../lib/utils/import');\nconst { FauxContext } = require('../../../helpers/faux-context');\nconst { parse: babelESLintParse } = require('../../../helpers/babel-eslint-parser');\n\ndescribe('getSourceModuleNameForIdentifier', () => {\n  describe('when the identifier is not imported', () => {\n    it('returns undefined', () => {\n      const context = new FauxContext(`\n      Foo;\n    `);\n\n      const node = { name: 'Foo', type: 'Identifier' };\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBeUndefined();\n    });\n  });\n\n  describe('when the identifier is imported', () => {\n    it('as a default export', () => {\n      const context = new FauxContext(`\n        import Foo from 'bar';\n\n        Foo;\n      `);\n\n      const node = { name: 'Foo', type: 'Identifier' };\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('bar');\n    });\n\n    it('as a named export', () => {\n      const context = new FauxContext(`\n        import { Foo } from 'bar';\n\n        Foo;\n      `);\n\n      const node = { name: 'Foo', type: 'Identifier' };\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('bar');\n    });\n\n    it('when aliasing a named export', () => {\n      const context = new FauxContext(`\n        import { SomeOtherThing as Foo } from 'bar';\n\n        Foo;\n      `);\n\n      const node = { name: 'Foo', type: 'Identifier' };\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('bar');\n    });\n\n    it('model.extend', () => {\n      const context = new FauxContext(`\n        import Model from '@ember-data/model';\n\n        Model.extend();\n      `);\n\n      const node = babelESLintParse('Model.extend({})').body[0].expression.callee;\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('@ember-data/model');\n    });\n\n    it('dS.Model.extend', () => {\n      const context = new FauxContext(`\n        import DS from 'ember-data';\n\n        DS.Model.extend();\n      `);\n\n      const node = babelESLintParse('DS.Model.extend({})').body[0].expression.callee;\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('ember-data');\n    });\n\n    it('some.Long.Chained.Path.extend', () => {\n      const context = new FauxContext(`\n        import Some from 'some-path';\n\n        Some.Long.Chained.Path.extend();\n      `);\n\n      const node = babelESLintParse('Some.Long.Chained.Path.extend({})').body[0].expression.callee;\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('some-path');\n    });\n\n    it('model.extend(Mixin)', () => {\n      const context = new FauxContext(`\n        import Mixin from './my-mixin';\n        import Model from '@ember-data/model';\n\n        export default class SomeClass extends Model.extend(Mixin) {}\n      `);\n\n      const node = babelESLintParse('Model.extend(Mixin)').body[0].expression;\n\n      expect(getSourceModuleNameForIdentifier(context, node)).toBe('@ember-data/model');\n    });\n  });\n});\n"
  },
  {
    "path": "tests/lib/utils/utils-test.js",
    "content": "'use strict';\n\nconst { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser');\nconst utils = require('../../../lib/utils/utils');\n\nfunction parse(code) {\n  return babelESLintParse(code).body[0].expression;\n}\n\nfunction parseVariableDeclarator(code) {\n  return babelESLintParse(code).body[0].declarations[0];\n}\n\ndescribe('collectObjectPatternBindings', () => {\n  it('collects bindings correctly', () => {\n    const node = parseVariableDeclarator('const { $ } = Ember');\n    const collectedBindings = utils.collectObjectPatternBindings(node, {\n      Ember: ['$'],\n    });\n\n    expect(collectedBindings).toStrictEqual(['$']);\n  });\n\n  it('collects aliased bindings correctly', () => {\n    const node = parseVariableDeclarator('const { $:foo } = Ember');\n    const collectedBindings = utils.collectObjectPatternBindings(node, {\n      Ember: ['$'],\n    });\n\n    expect(collectedBindings).toStrictEqual(['foo']);\n  });\n\n  it('collects only relevant bindings correctly for multiple destructurings', () => {\n    const node = parseVariableDeclarator('const { $, computed } = Ember');\n    const collectedBindings = utils.collectObjectPatternBindings(node, {\n      Ember: ['$'],\n    });\n\n    expect(collectedBindings).toStrictEqual(['$']);\n  });\n\n  it('collects only relevant bindings correctly for multiple destructurings and aliases', () => {\n    const node = parseVariableDeclarator('const { $: foo, computed } = Ember');\n    const collectedBindings = utils.collectObjectPatternBindings(node, {\n      Ember: ['$'],\n    });\n\n    expect(collectedBindings).toStrictEqual(['foo']);\n  });\n\n  it('collects multiple relevant bindings', () => {\n    const node = parseVariableDeclarator('const { $: foo, computed } = Ember');\n    const collectedBindings = utils.collectObjectPatternBindings(node, {\n      Ember: ['$', 'computed'],\n    });\n\n    expect(collectedBindings).toStrictEqual(['foo', 'computed']);\n  });\n});\n\ndescribe('findNodes', () => {\n  const node = parse(`test = [\n    {test: \"a\"}, b, \"c\", [d, e], \"f\", \"g\", h, {test: \"i\"}, function() {}, [], new Date()\n  ]`).right.elements;\n\n  it('should find nodes based on their name', () => {\n    const literals = utils.findNodes(node, 'Literal');\n    const identifiers = utils.findNodes(node, 'Identifier');\n    const objects = utils.findNodes(node, 'ObjectExpression');\n    const functions = utils.findNodes(node, 'FunctionExpression');\n    const news = utils.findNodes(node, 'NewExpression');\n    const arrays = utils.findNodes(node, 'ArrayExpression');\n\n    expect(literals).toHaveLength(3);\n    expect(identifiers).toHaveLength(2);\n    expect(objects).toHaveLength(2);\n    expect(functions).toHaveLength(1);\n    expect(news).toHaveLength(1);\n    expect(arrays).toHaveLength(2);\n  });\n});\n\ndescribe('function sort order', function () {\n  it('has exported functions in sorted order', function () {\n    expect(Object.getOwnPropertyNames(utils)).toStrictEqual(\n      Object.getOwnPropertyNames(utils).sort()\n    );\n  });\n});\n\ndescribe('getName', () => {\n  it('should behave correctly', () => {\n    expect(utils.getName(parse('x'))).toBe('x');\n    expect(utils.getName(parse('x()'))).toBe('x');\n    expect(utils.getName(parse('x?.()'))).toBe('x');\n    expect(utils.getName(parse('x.y'))).toBe('x.y');\n    expect(utils.getName(parse('x?.y'))).toBe('x.y');\n    expect(utils.getName(parse('x.y()'))).toBe('x.y');\n    expect(utils.getName(parse('x?.y?.()'))).toBe('x.y');\n  });\n});\n\ndescribe('getPropertyValue', () => {\n  const simpleObject = {\n    foo: true,\n    bar: {\n      baz: 1,\n      fizz: {\n        buzz: 'buzz',\n      },\n    },\n  };\n\n  const node = babelESLintParse(`\n    export default Ember.Component({\n      init() {\n        this._super(...arguments);\n        this._valueCache = this.value;\n        this.updated = false;\n      },\n      didReceiveAttrs() {\n        if (this._valueCache !== this.value) {\n          this._valueCache = this.value;\n          this.set('updated', true);\n        } else {\n          this.set('updated', false);\n        }\n      }\n    });\n  `).body[0].declaration;\n\n  it('should return null when property value not found for simpleObject', () => {\n    const value = utils.getPropertyValue(simpleObject, 'blah');\n    expect(value).toBeUndefined();\n  });\n\n  it('should return value when using a simple property path for simpleObject', () => {\n    const value = utils.getPropertyValue(simpleObject, 'foo');\n    expect(value).toBe(true);\n  });\n\n  it('should return value when using a full property path for simpleObject', () => {\n    const buzz = utils.getPropertyValue(simpleObject, 'bar.fizz.buzz');\n    expect(buzz).toBe('buzz');\n  });\n\n  it('should return null when property value not found for node', () => {\n    const value = utils.getPropertyValue(node, 'blah');\n    expect(value).toBeUndefined();\n  });\n\n  it('should return value when using a simple property path for node', () => {\n    const type = utils.getPropertyValue(node, 'type');\n    expect(type).toBe('CallExpression');\n  });\n\n  it('should return value when using a full property path for node', () => {\n    const name = utils.getPropertyValue(node, 'callee.object.name');\n    expect(name).toBe('Ember');\n  });\n});\n\ndescribe('getSize', () => {\n  const node = parse(\n    'some = {\\nfew: \"line\",\\nheight: \"statement\",\\nthat: \"should\",\\nhave: \"6 lines\",\\n};'\n  );\n\n  it('should check size of given expression', () => {\n    expect(utils.getSize(node)).toBe(6);\n  });\n});\n\ndescribe('parseArgs', () => {\n  it('should parse arguments', () => {\n    const node = parse('Ember.computed(\"asd\", \"qwe\", \"zxc\", function() {})');\n    const parsedArgs = utils.parseArgs(node);\n    expect(parsedArgs).toHaveLength(3);\n    expect(parsedArgs).toStrictEqual(['asd', 'qwe', 'zxc']);\n  });\n});\n\ndescribe('parseCallee', () => {\n  it('should parse calleeExpression', () => {\n    const node = parse('Ember.computed.or(\"asd\", \"qwe\")');\n    const parsedCallee = utils.parseCallee(node);\n    expect(parsedCallee).toHaveLength(3, 'it has 3 elements in array');\n    expect(parsedCallee).toStrictEqual(['Ember', 'computed', 'or']);\n  });\n\n  it('should parse newExpression', () => {\n    const node = parse('new Ember.A()');\n    const parsedCallee = utils.parseCallee(node);\n    expect(parsedCallee).toHaveLength(2, 'it has 2 elements in array');\n    expect(parsedCallee).toStrictEqual(['Ember', 'A']);\n  });\n});\n\ndescribe('startsWithThisExpression', () => {\n  it('should behave correctly', () => {\n    expect(utils.startsWithThisExpression(parse('x'))).toBeFalsy();\n    expect(utils.startsWithThisExpression(parse('x.y'))).toBeFalsy();\n    expect(utils.startsWithThisExpression(parse('x()'))).toBeFalsy();\n    expect(utils.startsWithThisExpression(parse('x.y()'))).toBeFalsy();\n    expect(utils.startsWithThisExpression(parse('x.y.z()'))).toBeFalsy();\n\n    expect(utils.startsWithThisExpression(parse('this'))).toBeTruthy();\n    expect(utils.startsWithThisExpression(parse('this.x'))).toBeTruthy();\n    expect(utils.startsWithThisExpression(parse('this.x.y'))).toBeTruthy();\n    expect(utils.startsWithThisExpression(parse('this.x()'))).toBeTruthy();\n    expect(utils.startsWithThisExpression(parse('this.x.y()'))).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "tests/lint.bench.mjs",
    "content": "/**\n * Benchmark script using mitata.\n *\n * Measures the time to lint Ember files of various sizes using the\n * recommended config (base for .js, gjs for .gjs, gts for .gts).\n *\n * When run standalone (`node --expose-gc tests/lint.bench.mjs`), it benchmarks\n * the local plugin only. When `bench-compare.mjs` passes `--control-dir <dir>`,\n * it also loads the control (base-branch) plugin from that directory and wraps\n * each size in a `summary()` so mitata shows a side-by-side comparison with\n * boxplots.\n *\n * Usage:\n *   node --expose-gc tests/lint.bench.mjs [--control-dir <path>]\n */\n\nimport { createRequire } from 'node:module';\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { resolve } from 'node:path';\nimport { run, bench, boxplot, summary, do_not_optimize as doNotOptimize } from 'mitata';\n\n// ---------------------------------------------------------------------------\n// CLI args\n// ---------------------------------------------------------------------------\n\nconst args = process.argv.slice(2);\nconst ctrlIdx = args.indexOf('--control-dir');\nconst CONTROL_DIR = ctrlIdx !== -1 ? resolve(args[ctrlIdx + 1]) : null;\n\n// ---------------------------------------------------------------------------\n// Build ESLint Linter configs for experiment (current branch)\n// ---------------------------------------------------------------------------\n\nconst require_ = createRequire(import.meta.url);\n\nfunction buildConfigs(pluginPath, parserPath) {\n  const plugin = require_(pluginPath);\n  const parser = require_(parserPath);\n\n  const baseRulesPath = resolve(pluginPath, '../recommended-rules.js');\n  const gjsRulesPath = resolve(pluginPath, '../recommended-rules-gjs.js');\n  const gtsRulesPath = resolve(pluginPath, '../recommended-rules-gts.js');\n\n  // Clear require cache to ensure fresh loads when comparing control vs experiment\n  delete require_.cache[require_.resolve(baseRulesPath)];\n  delete require_.cache[require_.resolve(gjsRulesPath)];\n  delete require_.cache[require_.resolve(gtsRulesPath)];\n\n  const baseRules = require_(baseRulesPath);\n  const gjsRules = require_(gjsRulesPath);\n  const gtsRules = require_(gtsRulesPath);\n\n  return {\n    js: {\n      plugins: { ember: plugin },\n      rules: { ...baseRules },\n    },\n    gjs: {\n      plugins: { ember: plugin },\n      languageOptions: {\n        parser,\n        parserOptions: {\n          ecmaFeatures: { modules: true },\n          ecmaVersion: 'latest',\n        },\n      },\n      processor: plugin.processors.noop,\n      rules: { ...baseRules, ...gjsRules },\n    },\n    gts: {\n      plugins: { ember: plugin },\n      languageOptions: {\n        parser,\n        parserOptions: {\n          extraFileExtensions: ['.gts'],\n        },\n      },\n      processor: plugin.processors.noop,\n      rules: { ...baseRules, ...gtsRules },\n    },\n  };\n}\n\nconst experimentConfigs = buildConfigs(\n  resolve(fileURLToPath(import.meta.url), '../../lib/index.js'),\n  'ember-eslint-parser'\n);\n\n// ---------------------------------------------------------------------------\n// (Optionally) load control (base branch) plugin from tmp dir\n// ---------------------------------------------------------------------------\n\nlet controlConfigs = null;\n\nif (CONTROL_DIR) {\n  const controlRequire = createRequire(resolve(CONTROL_DIR, 'index.js'));\n\n  // Load control plugin and parser from the control directory\n  const controlPluginPath = resolve(CONTROL_DIR, 'lib/index.js');\n  const controlParserPath = resolve(CONTROL_DIR, 'node_modules/ember-eslint-parser');\n\n  controlConfigs = buildConfigs(controlPluginPath, controlParserPath);\n}\n\n// ---------------------------------------------------------------------------\n// Fixture content\n// ---------------------------------------------------------------------------\n\nfunction fixture(name) {\n  return readFileSync(fileURLToPath(new URL(`./bench/${name}`, import.meta.url)), 'utf8');\n}\n\nconst FIXTURES = {\n  js: { small: fixture('small.js'), medium: fixture('medium.js'), large: fixture('large.js') },\n  gjs: { small: fixture('small.gjs'), medium: fixture('medium.gjs'), large: fixture('large.gjs') },\n  gts: { small: fixture('small.gts'), medium: fixture('medium.gts'), large: fixture('large.gts') },\n};\n\n// ---------------------------------------------------------------------------\n// Create Linter instances\n// ---------------------------------------------------------------------------\n\n// Use ESLint's Linter API for in-process linting\nconst { Linter } = require_('eslint');\n\nfunction createLinter(configs, type) {\n  const linter = new Linter({ configType: 'flat' });\n  return { linter, config: configs[type] };\n}\n\n// ---------------------------------------------------------------------------\n// Register benchmarks\n// ---------------------------------------------------------------------------\n\nconst FILE_TYPES = [\n  { type: 'js', ext: '.js' },\n  { type: 'gjs', ext: '.gjs' },\n  { type: 'gts', ext: '.gts' },\n];\n\nconst SIZES = ['small', 'medium', 'large'];\n\n// ---------------------------------------------------------------------------\n// JIT warm-up — lint every fixture with both configs so V8 compiles and\n// optimises the hot paths before any measurement begins.  Without this, the\n// first-to-run config pays the JIT compilation cost, creating order bias.\n// ---------------------------------------------------------------------------\n\nconst WARMUP_ROUNDS = 5;\n\nfor (const { type, ext } of FILE_TYPES) {\n  const { linter: expLinter, config: expConfig } = createLinter(experimentConfigs, type);\n  const ctrl = controlConfigs ? createLinter(controlConfigs, type) : null;\n\n  for (const size of SIZES) {\n    const code = FIXTURES[type][size];\n    const filename = `${size}${ext}`;\n\n    for (let i = 0; i < WARMUP_ROUNDS; i++) {\n      expLinter.verify(code, expConfig, { filename });\n      ctrl?.linter.verify(code, ctrl.config, { filename });\n    }\n  }\n}\n\nglobalThis.gc?.();\n\n// More iterations per sample → individual GC spikes get diluted, reducing\n// variance on noisy CI runners.  Scale down for larger fixtures so each\n// sample doesn't take too long (mitata needs many samples for stable stats).\n// Linting is heavier than parsing — use fewer iterations per sample than a\n// parser benchmark to keep each sample under a few seconds.  The .gjs/.gts\n// files are linted faster (smaller rule sets), but we use the same counts\n// to keep comparison fair across file types.\nconst BENCH_ITERS = { small: 50, medium: 25, large: 10 };\n\nfor (const { type, ext } of FILE_TYPES) {\n  const { linter: expLinter, config: expConfig } = createLinter(experimentConfigs, type);\n  const ctrl = controlConfigs ? createLinter(controlConfigs, type) : null;\n\n  for (const size of SIZES) {\n    const code = FIXTURES[type][size];\n    const filename = `${size}${ext}`;\n    const iters = BENCH_ITERS[size];\n\n    // Force a full GC before each benchmark group to reduce GC-triggered variance\n    globalThis.gc?.();\n\n    if (ctrl) {\n      // Side-by-side comparison with boxplots\n      boxplot(() => {\n        summary(() => {\n          bench(`${type} ${size} (control)`, () => {\n            for (let i = 0; i < iters; i++)\n              doNotOptimize(ctrl.linter.verify(code, ctrl.config, { filename }));\n          });\n          bench(`${type} ${size} (experiment)`, () => {\n            for (let i = 0; i < iters; i++)\n              doNotOptimize(expLinter.verify(code, expConfig, { filename }));\n          });\n        });\n      });\n    } else {\n      // Standalone mode — just benchmark the local plugin\n      bench(`${type} ${size}`, () => {\n        for (let i = 0; i < iters; i++)\n          doNotOptimize(expLinter.verify(code, expConfig, { filename }));\n      });\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Run\n// ---------------------------------------------------------------------------\n\nconst result = await run({ colors: false, throw: true });\n\n// Write JSON output if requested\nconst jsonPath = process.env.BENCH_JSON_OUTPUT;\nif (jsonPath) {\n  const { writeFileSync } = await import('node:fs');\n\n  const benchmarks = result.benchmarks.map((trial) => ({\n    alias: trial.alias,\n    runs: trial.runs.map((r) => ({\n      name: r.name,\n      args: r.args,\n      error: r.error ? { message: r.error.message || String(r.error) } : undefined,\n      stats: r.stats\n        ? {\n            avg: r.stats.avg,\n            min: r.stats.min,\n            max: r.stats.max,\n            p50: r.stats.p50,\n            p75: r.stats.p75,\n            p99: r.stats.p99,\n            samples: r.stats.samples,\n          }\n        : undefined,\n    })),\n  }));\n\n  writeFileSync(jsonPath, JSON.stringify({ context: result.context, benchmarks }, null, 2));\n}\n"
  },
  {
    "path": "tests/plugin-exports.js",
    "content": "'use strict';\n\nconst plugin = require('../lib');\nconst ember = require('../lib/utils/ember');\nconst base = require('../lib/config-legacy/base');\nconst recommended = require('../lib/config-legacy/recommended');\nconst recommendedGjs = require('../lib/config-legacy/recommended-gjs');\nconst recommendedGts = require('../lib/config-legacy/recommended-gts');\nconst templateLintMigration = require('../lib/config-legacy/template-lint-migration');\n\ndescribe('plugin exports', () => {\n  describe('utils', () => {\n    it('has the right util functions', () => {\n      expect(plugin.utils).toStrictEqual({ ember });\n    });\n  });\n\n  describe('configs', () => {\n    it('has the right configurations', () => {\n      expect(plugin.configs).toStrictEqual({\n        base,\n        recommended,\n        'recommended-gjs': recommendedGjs,\n        'recommended-gts': recommendedGts,\n        'template-lint-migration': templateLintMigration,\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tests/recommended.js",
    "content": "'use strict';\nconst rules = require('../lib').rules;\n\ndescribe('recommended rules', () => {\n  it('has the right list', () => {\n    const errors = [];\n\n    for (const name of Object.keys(rules)) {\n      if (rules[name].meta.docs.recommended) {\n        errors.push(name);\n      }\n    }\n\n    expect(errors).toMatchSnapshot();\n  });\n\n  it('gjs config has the right list', () => {\n    const errors = [];\n\n    for (const name of Object.keys(rules)) {\n      if (rules[name].meta.docs.recommendedGjs) {\n        errors.push(name);\n      }\n    }\n\n    expect(errors).toMatchSnapshot();\n  });\n\n  it('gts config has the right list', () => {\n    const errors = [];\n\n    for (const name of Object.keys(rules)) {\n      if (rules[name].meta.docs.recommendedGts) {\n        errors.push(name);\n      }\n    }\n\n    expect(errors).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "tests/rule-setup.js",
    "content": "'use strict';\n\nconst { readdirSync, readFileSync } = require('fs');\nconst path = require('path');\nconst rules = require('../lib').rules;\nconst recommendedRules = require('../lib/recommended-rules');\n\nconst RULE_NAMES = Object.keys(rules);\nconst RECOMMENDED_RULE_NAMES = Object.keys(recommendedRules).map((name) =>\n  name.replace('ember/', '')\n);\n\ndescribe('rules setup is correct', function () {\n  it('should have a list of exported rules and rules directory that match', function () {\n    const filePath = path.join(__dirname, '..', 'lib', 'rules');\n    const files = readdirSync(filePath);\n\n    expect(RULE_NAMES).toEqual(\n      files.filter((file) => !file.startsWith('.')).map((file) => file.replace('.js', ''))\n    );\n  });\n\n  it('should list all recommended rules in the recommended rules file', function () {\n    const recommendedRuleNamesFromMeta = RULE_NAMES.filter(\n      (key) => rules[key].meta.docs.recommended\n    );\n    expect(RECOMMENDED_RULE_NAMES).toStrictEqual(recommendedRuleNamesFromMeta);\n  });\n\n  it('should have tests for all rules', function () {\n    const filePath = path.join(__dirname, '..', 'tests', 'lib', 'rules');\n    const files = readdirSync(filePath);\n\n    expect(RULE_NAMES).toEqual(\n      files.filter((file) => !file.startsWith('.')).map((file) => file.replace('.js', ''))\n    );\n  });\n\n  describe('rule files', function () {\n    for (const ruleName of RULE_NAMES) {\n      const filePath = path.join(__dirname, '..', 'lib', 'rules', `${ruleName}.js`);\n      const file = readFileSync(filePath, 'utf8');\n\n      describe(ruleName, function () {\n        it('should have the jsdoc comment for rule type', function () {\n          expect(file).toContain(\"/** @type {import('eslint').Rule.RuleModule} */\");\n        });\n      });\n    }\n  });\n\n  describe('test files', function () {\n    for (const ruleName of RULE_NAMES) {\n      const filePath = path.join(__dirname, '..', 'tests', 'lib', 'rules', `${ruleName}.js`);\n      const file = readFileSync(filePath, 'utf8');\n\n      describe(ruleName, function () {\n        it('should have the right test suite name', function () {\n          expect(file).toContain(`.run('${ruleName}'`);\n        });\n      });\n    }\n  });\n\n  it('should have documentation for all rules', function () {\n    const filePath = path.join(__dirname, '..', 'docs', 'rules');\n    const files = readdirSync(filePath);\n\n    expect(RULE_NAMES).toEqual(\n      files\n        .filter((file) => !file.startsWith('.') && file !== '_TEMPLATE_.md')\n        .map((file) => file.replace('.md', ''))\n    );\n  });\n\n  it('should mention all rules in the README', function () {\n    const filePath = path.join(__dirname, '..', 'README.md');\n    const file = readFileSync(filePath, 'utf8');\n\n    for (const ruleName of RULE_NAMES) {\n      expect(file).toContain(ruleName);\n    }\n  });\n});\n"
  },
  {
    "path": "vitest.config.mjs",
    "content": "import { defineConfig } from 'vite';\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    setupFiles: [],\n    include: ['**/tests/**/*.js'],\n    exclude: ['tests/helpers/**', 'tests/bench/**', 'tests/fixtures/**', 'node_modules'],\n    sequence: {\n      hooks: 'list',\n    },\n    coverage: {\n      branches: 95,\n      functions: 98.95,\n      lines: 98,\n      statements: 98,\n    },\n  },\n});\n"
  }
]