├── .babelrc ├── .editorconfig ├── .eslint-doc-generatorrc.js ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── plan-release.yml │ └── publish.yml ├── .gitignore ├── .markdownlint.json ├── .markdownlintignore ├── .npmpackagejsonlintrc.json ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .release-plan.json ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── docs ├── rules │ ├── _TEMPLATE_.md │ ├── alias-model-in-controller.md │ ├── avoid-leaking-state-in-ember-objects.md │ ├── avoid-using-needs-in-controllers.md │ ├── classic-decorator-hooks.md │ ├── classic-decorator-no-classic-methods.md │ ├── closure-actions.md │ ├── computed-property-getters.md │ ├── jquery-ember-run.md │ ├── named-functions-in-promises.md │ ├── new-module-imports.md │ ├── no-actions-hash.md │ ├── no-array-prototype-extensions.md │ ├── no-arrow-function-computed-properties.md │ ├── no-assignment-of-untracked-properties-used-in-tracking-contexts.md │ ├── no-at-ember-render-modifiers.md │ ├── no-attrs-in-components.md │ ├── no-attrs-snapshot.md │ ├── no-capital-letters-in-routes.md │ ├── no-classic-classes.md │ ├── no-classic-components.md │ ├── no-component-lifecycle-hooks.md │ ├── no-computed-properties-in-native-classes.md │ ├── no-controller-access-in-routes.md │ ├── no-controllers.md │ ├── no-current-route-name.md │ ├── no-deeply-nested-dependent-keys-with-each.md │ ├── no-deprecated-router-transition-methods.md │ ├── no-duplicate-dependent-keys.md │ ├── no-ember-super-in-es-classes.md │ ├── no-ember-testing-in-module-scope.md │ ├── no-empty-attrs.md │ ├── no-empty-glimmer-component-classes.md │ ├── no-function-prototype-extensions.md │ ├── no-get-with-default.md │ ├── no-get.md │ ├── no-global-jquery.md │ ├── no-html-safe.md │ ├── no-implicit-injections.md │ ├── no-implicit-service-injection-argument.md │ ├── no-incorrect-calls-with-inline-anonymous-functions.md │ ├── no-incorrect-computed-macros.md │ ├── no-invalid-debug-function-arguments.md │ ├── no-invalid-dependent-keys.md │ ├── no-invalid-test-waiters.md │ ├── no-jquery.md │ ├── no-legacy-test-waiters.md │ ├── no-mixins.md │ ├── no-new-mixins.md │ ├── no-noop-setup-on-error-in-before.md │ ├── no-observers.md │ ├── no-old-shims.md │ ├── no-on-calls-in-components.md │ ├── no-pause-test.md │ ├── no-private-routing-service.md │ ├── no-proxies.md │ ├── no-replace-test-comments.md │ ├── no-restricted-property-modifications.md │ ├── no-restricted-resolver-tests.md │ ├── no-restricted-service-injections.md │ ├── no-runloop.md │ ├── no-settled-after-test-helper.md │ ├── no-shadow-route-definition.md │ ├── no-side-effects.md │ ├── no-string-prototype-extensions.md │ ├── no-test-and-then.md │ ├── no-test-import-export.md │ ├── no-test-module-for.md │ ├── no-test-support-import.md │ ├── no-test-this-render.md │ ├── no-tracked-properties-from-args.md │ ├── no-try-invoke.md │ ├── no-unnecessary-index-route.md │ ├── no-unnecessary-route-path-option.md │ ├── no-unnecessary-service-injection-argument.md │ ├── no-unused-services.md │ ├── no-volatile-computed-properties.md │ ├── order-in-components.md │ ├── order-in-controllers.md │ ├── order-in-models.md │ ├── order-in-routes.md │ ├── prefer-ember-test-helpers.md │ ├── require-async-inverse-relationship.md │ ├── require-computed-macros.md │ ├── require-computed-property-dependencies.md │ ├── require-fetch-import.md │ ├── require-return-from-computed.md │ ├── require-super-in-lifecycle-hooks.md │ ├── require-tagless-components.md │ ├── require-valid-css-selector-in-test-helpers.md │ ├── route-path-style.md │ ├── routes-segments-snake-case.md │ ├── template-indent.md │ ├── template-no-let-reference.md │ ├── use-brace-expansion.md │ ├── use-ember-data-rfc-395-imports.md │ └── use-ember-get-and-set.md └── svgs │ ├── gjs.svg │ └── gts.svg ├── eslint-remote-tester.config.js ├── eslint.config.js ├── lib ├── config-legacy │ ├── base.js │ ├── recommended-gjs.js │ ├── recommended-gts.js │ └── recommended.js ├── config │ ├── base.js │ ├── recommended-gjs.js │ ├── recommended-gts.js │ └── recommended.js ├── index.js ├── recommended-rules-gjs.js ├── recommended-rules-gts.js ├── recommended-rules.js ├── recommended.mjs ├── rules │ ├── alias-model-in-controller.js │ ├── avoid-leaking-state-in-ember-objects.js │ ├── avoid-using-needs-in-controllers.js │ ├── classic-decorator-hooks.js │ ├── classic-decorator-no-classic-methods.js │ ├── closure-actions.js │ ├── computed-property-getters.js │ ├── jquery-ember-run.js │ ├── named-functions-in-promises.js │ ├── new-module-imports.js │ ├── no-actions-hash.js │ ├── no-array-prototype-extensions.js │ ├── no-arrow-function-computed-properties.js │ ├── no-assignment-of-untracked-properties-used-in-tracking-contexts.js │ ├── no-at-ember-render-modifiers.js │ ├── no-attrs-in-components.js │ ├── no-attrs-snapshot.js │ ├── no-capital-letters-in-routes.js │ ├── no-classic-classes.js │ ├── no-classic-components.js │ ├── no-component-lifecycle-hooks.js │ ├── no-computed-properties-in-native-classes.js │ ├── no-controller-access-in-routes.js │ ├── no-controllers.js │ ├── no-current-route-name.js │ ├── no-deeply-nested-dependent-keys-with-each.js │ ├── no-deprecated-router-transition-methods.js │ ├── no-duplicate-dependent-keys.js │ ├── no-ember-super-in-es-classes.js │ ├── no-ember-testing-in-module-scope.js │ ├── no-empty-attrs.js │ ├── no-empty-glimmer-component-classes.js │ ├── no-function-prototype-extensions.js │ ├── no-get-with-default.js │ ├── no-get.js │ ├── no-global-jquery.js │ ├── no-html-safe.js │ ├── no-implicit-injections.js │ ├── no-implicit-service-injection-argument.js │ ├── no-incorrect-calls-with-inline-anonymous-functions.js │ ├── no-incorrect-computed-macros.js │ ├── no-invalid-debug-function-arguments.js │ ├── no-invalid-dependent-keys.js │ ├── no-invalid-test-waiters.js │ ├── no-jquery.js │ ├── no-legacy-test-waiters.js │ ├── no-mixins.js │ ├── no-new-mixins.js │ ├── no-noop-setup-on-error-in-before.js │ ├── no-observers.js │ ├── no-old-shims.js │ ├── no-on-calls-in-components.js │ ├── no-pause-test.js │ ├── no-private-routing-service.js │ ├── no-proxies.js │ ├── no-replace-test-comments.js │ ├── no-restricted-property-modifications.js │ ├── no-restricted-resolver-tests.js │ ├── no-restricted-service-injections.js │ ├── no-runloop.js │ ├── no-settled-after-test-helper.js │ ├── no-shadow-route-definition.js │ ├── no-side-effects.js │ ├── no-string-prototype-extensions.js │ ├── no-test-and-then.js │ ├── no-test-import-export.js │ ├── no-test-module-for.js │ ├── no-test-support-import.js │ ├── no-test-this-render.js │ ├── no-tracked-properties-from-args.js │ ├── no-try-invoke.js │ ├── no-unnecessary-index-route.js │ ├── no-unnecessary-route-path-option.js │ ├── no-unnecessary-service-injection-argument.js │ ├── no-unused-services.js │ ├── no-volatile-computed-properties.js │ ├── order-in-components.js │ ├── order-in-controllers.js │ ├── order-in-models.js │ ├── order-in-routes.js │ ├── prefer-ember-test-helpers.js │ ├── require-async-inverse-relationship.js │ ├── require-computed-macros.js │ ├── require-computed-property-dependencies.js │ ├── require-fetch-import.js │ ├── require-return-from-computed.js │ ├── require-super-in-lifecycle-hooks.js │ ├── require-tagless-components.js │ ├── require-valid-css-selector-in-test-helpers.js │ ├── route-path-style.js │ ├── routes-segments-snake-case.js │ ├── template-indent.js │ ├── template-no-let-reference.js │ ├── use-brace-expansion.js │ ├── use-ember-data-rfc-395-imports.js │ └── use-ember-get-and-set.js └── utils │ ├── computed-properties.js │ ├── computed-property-dependent-keys.js │ ├── computed-property-macros.js │ ├── decorators.js │ ├── ember.js │ ├── fixer.js │ ├── import.js │ ├── javascript.js │ ├── jquery-methods.js │ ├── jquery.js │ ├── new-module.js │ ├── property-getter.js │ ├── property-order.js │ ├── property-setter.js │ ├── scope-references-this.js │ ├── stack.js │ ├── types.js │ └── utils.js ├── package.json ├── pnpm-lock.yaml ├── scripts ├── list-jquery-methods.js └── update-rules.js ├── tests ├── __snapshots__ │ └── recommended.js.snap ├── config-setup.js ├── helpers │ ├── babel-eslint-parser.js │ ├── faux-context.js │ └── test-case.js ├── lib │ ├── rules-preprocessor │ │ ├── ember_ts │ │ │ ├── bar.gts │ │ │ ├── baz.ts │ │ │ └── foo.gts │ │ ├── gjs-gts-parser-test.js │ │ ├── my-component.gts │ │ └── tsconfig.eslint.json │ ├── rules │ │ ├── alias-model-in-controller.js │ │ ├── avoid-leaking-state-in-ember-objects.js │ │ ├── avoid-using-needs-in-controllers.js │ │ ├── classic-decorator-hooks.js │ │ ├── classic-decorator-no-classic-methods.js │ │ ├── closure-actions.js │ │ ├── computed-property-getters.js │ │ ├── jquery-ember-run.js │ │ ├── named-functions-in-promises.js │ │ ├── new-module-imports.js │ │ ├── no-actions-hash.js │ │ ├── no-array-prototype-extensions.js │ │ ├── no-arrow-function-computed-properties.js │ │ ├── no-assignment-of-untracked-properties-used-in-tracking-contexts.js │ │ ├── no-at-ember-render-modifiers.js │ │ ├── no-attrs-in-components.js │ │ ├── no-attrs-snapshot.js │ │ ├── no-capital-letters-in-routes.js │ │ ├── no-classic-classes.js │ │ ├── no-classic-components.js │ │ ├── no-component-lifecycle-hooks.js │ │ ├── no-computed-properties-in-native-classes.js │ │ ├── no-controller-access-in-routes.js │ │ ├── no-controllers.js │ │ ├── no-current-route-name.js │ │ ├── no-deeply-nested-dependent-keys-with-each.js │ │ ├── no-deprecated-router-transition-methods.js │ │ ├── no-duplicate-dependent-keys.js │ │ ├── no-ember-super-in-es-classes.js │ │ ├── no-ember-testing-in-module-scope.js │ │ ├── no-empty-attrs.js │ │ ├── no-empty-glimmer-component-classes.js │ │ ├── no-function-prototype-extensions.js │ │ ├── no-get-with-default.js │ │ ├── no-get.js │ │ ├── no-global-jquery.js │ │ ├── no-html-safe.js │ │ ├── no-implicit-injections.js │ │ ├── no-implicit-service-injection-argument.js │ │ ├── no-incorrect-calls-with-inline-anonymous-functions.js │ │ ├── no-incorrect-computed-macros.js │ │ ├── no-invalid-debug-function-arguments.js │ │ ├── no-invalid-dependent-keys.js │ │ ├── no-invalid-test-waiters.js │ │ ├── no-jquery.js │ │ ├── no-legacy-test-waiters.js │ │ ├── no-mixins.js │ │ ├── no-new-mixins.js │ │ ├── no-noop-setup-on-error-in-before.js │ │ ├── no-observers.js │ │ ├── no-old-shims.js │ │ ├── no-on-calls-in-components.js │ │ ├── no-pause-test.js │ │ ├── no-private-routing-service.js │ │ ├── no-proxies.js │ │ ├── no-replace-test-comments.js │ │ ├── no-restricted-property-modifications.js │ │ ├── no-restricted-resolver-tests.js │ │ ├── no-restricted-service-injections.js │ │ ├── no-runloop.js │ │ ├── no-settled-after-test-helper.js │ │ ├── no-shadow-route-definition.js │ │ ├── no-side-effects.js │ │ ├── no-string-prototype-extensions.js │ │ ├── no-test-and-then.js │ │ ├── no-test-import-export.js │ │ ├── no-test-module-for.js │ │ ├── no-test-support-import.js │ │ ├── no-test-this-render.js │ │ ├── no-tracked-properties-from-args.js │ │ ├── no-try-invoke.js │ │ ├── no-unnecessary-index-route.js │ │ ├── no-unnecessary-route-path-option.js │ │ ├── no-unnecessary-service-injection-argument.js │ │ ├── no-unused-services.js │ │ ├── no-volatile-computed-properties.js │ │ ├── order-in-components.js │ │ ├── order-in-controllers.js │ │ ├── order-in-models.js │ │ ├── order-in-routes.js │ │ ├── prefer-ember-test-helpers.js │ │ ├── require-async-inverse-relationship.js │ │ ├── require-computed-macros.js │ │ ├── require-computed-property-dependencies.js │ │ ├── require-fetch-import.js │ │ ├── require-return-from-computed.js │ │ ├── require-super-in-lifecycle-hooks.js │ │ ├── require-tagless-components.js │ │ ├── require-valid-css-selector-in-test-helpers.js │ │ ├── route-path-style.js │ │ ├── routes-segments-snake-case.js │ │ ├── template-indent.js │ │ ├── template-no-let-reference.js │ │ ├── use-brace-expansion.js │ │ ├── use-ember-data-rfc-395-imports.js │ │ └── use-ember-get-and-set.js │ └── utils │ │ ├── computed-properties-test.js │ │ ├── computed-property-dependent-keys-test.js │ │ ├── computed-property-macros-test.js │ │ ├── decorators-test.js │ │ ├── ember-test.js │ │ ├── import-test.js │ │ ├── javascript-test.js │ │ ├── new-module-test.js │ │ ├── property-getter-test.js │ │ ├── property-order-test.js │ │ ├── property-setter-test.js │ │ ├── scope-references-this-test.js │ │ ├── types-test.js │ │ ├── utils-test.js │ │ └── utils │ │ └── get-source-module-name-for-identifier-test.js ├── plugin-exports.js ├── recommended.js └── rule-setup.js └── vitest.config.mjs /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.{diff,md}] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslint-doc-generatorrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint-doc-generator').GenerateOptions} */ 2 | module.exports = { 3 | configEmoji: [ 4 | ['recommended-gjs', '![gjs logo](/docs/svgs/gjs.svg)'], 5 | ['recommended-gts', '![gts logo](/docs/svgs/gts.svg)'], 6 | ], 7 | ruleDocSectionInclude: ['Examples'], 8 | ruleDocTitleFormat: 'prefix-name', 9 | ruleListSplit: 'meta.docs.category', 10 | urlConfigs: 'https://github.com/ember-cli/eslint-plugin-ember#-configurations', 11 | }; 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: 'npm' 6 | directory: '/' 7 | schedule: 8 | interval: 'weekly' 9 | open-pull-requests-limit: 10 10 | 11 | - package-ecosystem: 'github-actions' 12 | directory: '/' 13 | schedule: 14 | interval: 'weekly' 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | self-lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: pnpm/action-setup@v4 18 | with: 19 | run_install: false 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 22.x 23 | cache: 'pnpm' 24 | 25 | - run: pnpm install 26 | - run: pnpm lint 27 | 28 | build: 29 | runs-on: ${{ matrix.os }}-latest 30 | 31 | strategy: 32 | matrix: 33 | os: [ ubuntu, windows ] 34 | node-version: [18.x, 20.x, 22.x] 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: pnpm/action-setup@v4 39 | with: 40 | run_install: false 41 | - name: Use Node.js ${{ matrix.node-version }} 42 | uses: actions/setup-node@v4 43 | with: 44 | node-version: ${{ matrix.node-version }} 45 | cache: 'pnpm' 46 | 47 | - run: pnpm install 48 | - run: pnpm test:coverage 49 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # For every push to the master branch, this checks if the release-plan was 2 | # updated and if it was it will publish stable npm packages based on the 3 | # release plan 4 | 5 | name: Publish Stable 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | 14 | concurrency: 15 | group: publish-${{ github.head_ref || github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | check-plan: 20 | name: "Check Release Plan" 21 | runs-on: ubuntu-latest 22 | outputs: 23 | command: ${{ steps.check-release.outputs.command }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | ref: 'master' 30 | # This will only cause the `check-plan` job to have a result of `success` 31 | # when the .release-plan.json file was changed on the last commit. This 32 | # plus the fact that this action only runs on main will be enough of a guard 33 | - id: check-release 34 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 35 | 36 | publish: 37 | name: "NPM Publish" 38 | runs-on: ubuntu-latest 39 | needs: check-plan 40 | if: needs.check-plan.outputs.command == 'release' 41 | permissions: 42 | contents: write 43 | pull-requests: write 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-node@v4 48 | with: 49 | node-version: 18 50 | # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable 51 | registry-url: 'https://registry.npmjs.org' 52 | - uses: pnpm/action-setup@v4 53 | - run: pnpm install --frozen-lockfile 54 | - name: npm publish 55 | run: pnpm release-plan publish 56 | env: 57 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 58 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Dependencies 6 | node_modules 7 | 8 | # Misc 9 | .eslintcache 10 | coverage 11 | npm-debug.log 12 | *.swp 13 | .vscode 14 | 15 | # eslint-remote-tester 16 | eslint-remote-tester-results 17 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "line-length": false, 3 | "ul-style": { 4 | "style": "dash" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | LICENSE.md 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmpackagejsonlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-properties": "error", 4 | "no-repeated-dependencies": "error", 5 | "prefer-alphabetical-bundledDependencies": "error", 6 | "prefer-alphabetical-dependencies": "error", 7 | "prefer-alphabetical-devDependencies": "error", 8 | "prefer-alphabetical-optionalDependencies": "error", 9 | "prefer-alphabetical-scripts": "error", 10 | "prefer-caret-version-dependencies": "error", 11 | "prefer-caret-version-devDependencies": "error", 12 | "prefer-scripts": ["error", ["lint", "test"]] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # npm / pnpm settings here 2 | 3 | # as a library, we want to make sure we explicitly handle peers, 4 | # and not rely on hidden behavior of package-managers. 5 | auto-install-peers=false 6 | strict-peer-dependents=true 7 | dedupe-peer-dependents=true 8 | prefer-workspaces-packages=true 9 | resolve-peers-from-workspace-root=false 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib/recommended-rules.js 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | printWidth: 100, 5 | semi: true, 6 | singleQuote: true, 7 | trailingComma: 'es5', 8 | }; 9 | -------------------------------------------------------------------------------- /.release-plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "solution": { 3 | "eslint-plugin-ember": { 4 | "impact": "minor", 5 | "oldVersion": "12.4.0", 6 | "newVersion": "12.5.0", 7 | "constraints": [ 8 | { 9 | "impact": "minor", 10 | "reason": "Appears in changelog section :rocket: Enhancement" 11 | }, 12 | { 13 | "impact": "patch", 14 | "reason": "Appears in changelog section :house: Internal" 15 | } 16 | ], 17 | "pkgJSONPath": "./package.json" 18 | } 19 | }, 20 | "description": "## 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" 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2016-2017 Netguru Sp. z o.o. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases 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. 4 | 5 | ## Preparation 6 | 7 | Since the majority of the actual release process is automated, the remaining tasks before releasing are: 8 | 9 | - correctly labeling **all** pull requests that have been merged since the last release 10 | - updating pull request titles so they make sense to our users 11 | 12 | Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall 13 | guiding principle here is that changelogs are for humans, not machines. 14 | 15 | When reviewing merged PR's the labels to be used are: 16 | 17 | - breaking - Used when the PR is considered a breaking change. 18 | - enhancement - Used when the PR adds a new feature or enhancement. 19 | - bug - Used when the PR fixes a bug included in a previous release. 20 | - documentation - Used when the PR adds or updates documentation. 21 | - internal - Internal changes or things that don't fit in any other category. 22 | 23 | **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` 24 | 25 | ## Release 26 | 27 | Once 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 28 | -------------------------------------------------------------------------------- /docs/rules/_TEMPLATE_.md: -------------------------------------------------------------------------------- 1 | # TODO: rule-name-goes-here 2 | 3 | (TODO: only include this line if the rule is recommended) ✅ The `"extends": "plugin:ember/recommended"` property in a configuration file enables this rule. 4 | 5 | (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. 6 | 7 | TODO: context about the problem goes here 8 | 9 | ## Rule Details 10 | 11 | TODO: what the rule does goes here 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | // TODO: Example 1 19 | ``` 20 | 21 | ```js 22 | // TODO: Example 2 23 | ``` 24 | 25 | Examples of **correct** code for this rule: 26 | 27 | ```js 28 | // TODO: Example 1 29 | ``` 30 | 31 | ```js 32 | // TODO: Example 2 33 | ``` 34 | 35 | ## Migration 36 | 37 | TODO: suggest any fast/automated techniques for fixing violations in a large codebase 38 | 39 | - TODO: suggestion on how to fix violations using find-and-replace / regexp 40 | - TODO: suggestion on how to fix violations using a codemod 41 | 42 | ## Configuration 43 | 44 | 45 | TODO: exclude this section if the rule has no extra configuration. 46 | TODO: ensure `meta.schema` contains descriptions, constraints, defaults, etc for all options. 47 | 48 | 49 | ## Related Rules 50 | 51 | - [TODO-related-rule-name1](related-rule-name1.md) 52 | - [TODO-related-rule-name2](related-rule-name2.md) 53 | 54 | ## References 55 | 56 | - TODO: link to relevant documentation goes here) 57 | - TODO: link to relevant function spec goes here 58 | - TODO: link to relevant guide goes here 59 | -------------------------------------------------------------------------------- /docs/rules/alias-model-in-controller.md: -------------------------------------------------------------------------------- 1 | # ember/alias-model-in-controller 2 | 3 | 4 | 5 | It makes code more readable if the model has the same name as a subject. 6 | 7 | ## Examples 8 | 9 | We can do this in two ways: 10 | 11 | - Alias the model to another property name in the Controller: 12 | 13 | ```js 14 | import Controller from '@ember/controller'; 15 | import { alias } from '@ember/object/computed'; 16 | 17 | export default Controller.extend({ 18 | nail: alias('model') 19 | }); 20 | ``` 21 | 22 | - Set it as a property in the Route's `setupController` method: 23 | 24 | ```js 25 | import Route from '@ember/routing/route'; 26 | 27 | export default Route.extend({ 28 | setupController(controller, model) { 29 | controller.set('nail', model); 30 | } 31 | }); 32 | ``` 33 | 34 | If 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: 35 | 36 | ```js 37 | import Controller from '@ember/controller'; 38 | import { reads } from '@ember/object/computed'; 39 | 40 | export default Controller.extend({ 41 | people: reads('model.people'), 42 | pets: reads('model.pets') 43 | }); 44 | ``` 45 | 46 | ## Help Wanted 47 | 48 | | Issue | Link | 49 | | :-- | :-- | 50 | | ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) | 51 | -------------------------------------------------------------------------------- /docs/rules/avoid-using-needs-in-controllers.md: -------------------------------------------------------------------------------- 1 | # ember/avoid-using-needs-in-controllers 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Avoid using `needs` to load other controllers. Inject the required controller instead. `needs` was deprecated in ember 1.x and removed in 2.0. 8 | 9 | ## Examples 10 | 11 | Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | export default Controller.extend({ 15 | needs: ['comments'], 16 | newComments: alias('controllers.comments.newest') 17 | }); 18 | ``` 19 | 20 | Examples of **correct** code for this rule: 21 | 22 | ```js 23 | import Controller, { inject as controller } from '@ember/controller'; 24 | 25 | export default Component.extend({ 26 | comments: controller(), 27 | newComments: alias('comments.newest') 28 | }); 29 | ``` 30 | 31 | ## Help Wanted 32 | 33 | | Issue | Link | 34 | | :-- | :-- | 35 | | ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) | 36 | -------------------------------------------------------------------------------- /docs/rules/classic-decorator-hooks.md: -------------------------------------------------------------------------------- 1 | # ember/classic-decorator-hooks 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Use the correct lifecycle hooks in classic and non-classic classes. Classic 8 | classes should use `init`, and non-classic classes should use `constructor`. 9 | Additionally, non-classic classes may not use `destroy`. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | export default class MyService extends Service { 17 | init() { 18 | // ... 19 | } 20 | 21 | destroy() { 22 | // ... 23 | } 24 | } 25 | ``` 26 | 27 | ```js 28 | @classic 29 | export default class MyService extends Service { 30 | constructor(...args) { 31 | super(...args); 32 | // ... 33 | } 34 | } 35 | ``` 36 | 37 | Examples of **correct** code for this rule: 38 | 39 | ```js 40 | @classic 41 | export default class MyService extends Service { 42 | init() { 43 | // ... 44 | } 45 | 46 | destroy() { 47 | // ... 48 | } 49 | } 50 | ``` 51 | 52 | ```js 53 | export default class MyService extends Service { 54 | constructor(...args) { 55 | super(...args); 56 | // ... 57 | } 58 | 59 | willDestroy() { 60 | // ... 61 | } 62 | } 63 | ``` 64 | 65 | ## References 66 | 67 | - [ember-classic-decorator](https://github.com/pzuraq/ember-classic-decorator) 68 | 69 | ## Related Rules 70 | 71 | - [classic-decorator-no-classic-methods](classic-decorator-no-classic-methods.md) 72 | -------------------------------------------------------------------------------- /docs/rules/classic-decorator-no-classic-methods.md: -------------------------------------------------------------------------------- 1 | # ember/classic-decorator-no-classic-methods 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Disallows the use of the following classic API methods within a class: 8 | 9 | - `get` 10 | - `set` 11 | - `getProperties` 12 | - `setProperties` 13 | - `getWithDefault` 14 | - `incrementProperty` 15 | - `decrementProperty` 16 | - `toggleProperty` 17 | - `addObserver` 18 | - `removeObserver` 19 | - `notifyPropertyChange` 20 | - `cacheFor` 21 | - `proto` 22 | 23 | These are "classic" API methods, and their usage is discouraged in Octane. 24 | Non-method versions of them can still be used, e.g. `@ember/object#get` and 25 | `@ember/object#set` instead of `this.get` and `this.set`. 26 | 27 | ## Examples 28 | 29 | Examples of **incorrect** code for this rule: 30 | 31 | ```js 32 | export default class MyService extends Service { 33 | constructor(...args) { 34 | super(...args); 35 | this.set('foo', 'bar'); 36 | } 37 | } 38 | ``` 39 | 40 | Examples of **correct** code for this rule: 41 | 42 | ```js 43 | @classic 44 | export default class MyService extends Service { 45 | constructor(...args) { 46 | super(...args); 47 | this.set('foo', 'bar'); 48 | } 49 | } 50 | ``` 51 | 52 | ```js 53 | import { set } from '@ember/object'; 54 | 55 | export default class MyService extends Service { 56 | constructor(...args) { 57 | super(...args); 58 | set(this, 'foo', 'bar'); 59 | } 60 | } 61 | ``` 62 | 63 | ```js 64 | import { tracked } from '@glimmer/tracking'; 65 | 66 | export default class MyService extends Service { 67 | @tracked foo = 'bar'; 68 | } 69 | ``` 70 | 71 | ## References 72 | 73 | - [ember-classic-decorator](https://github.com/pzuraq/ember-classic-decorator) 74 | 75 | ## Related Rules 76 | 77 | - [no-get](no-get.md) 78 | - [classic-decorator-hooks](classic-decorator-hooks.md) 79 | - [Tracked properties](https://guides.emberjs.com/release/upgrading/current-edition/tracked-properties/) 80 | -------------------------------------------------------------------------------- /docs/rules/closure-actions.md: -------------------------------------------------------------------------------- 1 | # ember/closure-actions 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Always use closure actions (according to DDAU convention). Exception: only when you need bubbling. 8 | 9 | ## Examples 10 | 11 | ```js 12 | export default Controller.extend({ 13 | actions: { 14 | detonate() { 15 | alert('Kabooom'); 16 | } 17 | } 18 | }); 19 | ``` 20 | 21 | Examples of **incorrect** code for this rule: 22 | 23 | ```hbs 24 | {{awful-component detonate='detonate'}} 25 | ``` 26 | 27 | ```js 28 | // awful-component.js 29 | export default Component.extend({ 30 | actions: { 31 | pushLever() { 32 | this.sendAction('detonate'); 33 | } 34 | } 35 | }); 36 | ``` 37 | 38 | Examples of **correct** code for this rule: 39 | 40 | ```hbs 41 | {{pretty-component boom=(action 'detonate')}} 42 | ``` 43 | 44 | ```js 45 | // pretty-component.js 46 | export default Component.extend({ 47 | actions: { 48 | pushLever() { 49 | this.boom(); 50 | } 51 | } 52 | }); 53 | ``` 54 | 55 | ## References 56 | 57 | - [RFC](https://github.com/emberjs/rfcs/blob/master/text/0335-deprecate-send-action.md) to deprecate `sendAction` 58 | -------------------------------------------------------------------------------- /docs/rules/jquery-ember-run.md: -------------------------------------------------------------------------------- 1 | # ember/jquery-ember-run 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Don’t use jQuery without the Ember Run Loop. 8 | 9 | Using 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). 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | import $ from 'jquery'; 17 | 18 | $('#something-rendered-by-jquery-plugin').on('click', () => { 19 | this._handlerActionFromController(); 20 | }); 21 | ``` 22 | 23 | Examples of **correct** code for this rule: 24 | 25 | ```js 26 | import $ from 'jquery'; 27 | import { bind } from '@ember/runloop'; 28 | 29 | $('#something-rendered-by-jquery-plugin').on( 30 | 'click', 31 | bind(this, this._handlerActionFromController) 32 | ); 33 | ``` 34 | 35 | ## Related 36 | 37 | - [no-global-jquery](./no-global-jquery.md) 38 | - [no-jquery](./no-jquery.md) 39 | -------------------------------------------------------------------------------- /docs/rules/new-module-imports.md: -------------------------------------------------------------------------------- 1 | # ember/new-module-imports 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Use "New Module Imports" from Ember RFC #176. 8 | 9 | [RFC #176](https://github.com/emberjs/rfcs/pull/176) introduced as new public 10 | API for Ember.js based on ES6 module imports. 11 | 12 | If you use `ember-cli-babel` with version `6.6.0` or above you can start using 13 | the "New Module Imports" instead of the `Ember` global directly. This will 14 | enable us to build better tree shaking feature into Ember CLI. 15 | 16 | If 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. 17 | 18 | ## Examples 19 | 20 | Examples of **incorrect** code for this rule: 21 | 22 | ```js 23 | Ember.Component.extend({}); 24 | Ember.Object.extend({}); 25 | Ember.computed(function () {}); 26 | Ember.inject.service('foo'); 27 | ``` 28 | 29 | Examples of **correct** code for this rule: 30 | 31 | ```js 32 | import Component from '@ember/component'; 33 | import EmberObject, { computed } from '@ember/object'; 34 | import Service, { inject } from '@ember/service'; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/no-actions-hash.md: -------------------------------------------------------------------------------- 1 | # ember/no-actions-hash 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Disallows the actions hash in components and controllers. 8 | 9 | Ember 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. 10 | 11 | ## Rule Detail 12 | 13 | Use the `@action` decorator or `foo: action(function() {}))` syntax instead of an `actions` hash. 14 | 15 | ## Examples 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```js 20 | // Bad, with classic class 21 | import Component from '@ember/component'; 22 | 23 | export default Component.extend({ 24 | actions: { 25 | foo() {} 26 | } 27 | }); 28 | ``` 29 | 30 | ```js 31 | // Bad, with native class 32 | import Component from '@ember/component'; 33 | 34 | export class MyComponent extends Component { 35 | actions = { 36 | foo() {} 37 | }; 38 | } 39 | ``` 40 | 41 | Examples of **correct** code for this rule: 42 | 43 | ```js 44 | // Good, with classic class 45 | import Component from '@ember/component'; 46 | import { action } from '@ember/object'; 47 | 48 | export default Component.extend({ 49 | foo: action(function () {}) 50 | }); 51 | ``` 52 | 53 | ```js 54 | // Good, with native class 55 | import Component from '@ember/component'; 56 | import { action } from '@ember/object'; 57 | 58 | export class MyComponent extends Component { 59 | @action 60 | foo() {} 61 | } 62 | ``` 63 | 64 | ## Further Reading 65 | 66 | - [`{{on}}` Modifier RFC](https://github.com/emberjs/rfcs/pull/471) 67 | - [`{{fn}}` Helper RFC](https://github.com/emberjs/rfcs/pull/470) 68 | - [Ember Octane Update: What's up with `@action`?](https://www.pzuraq.com/ember-octane-update-action/) 69 | -------------------------------------------------------------------------------- /docs/rules/no-arrow-function-computed-properties.md: -------------------------------------------------------------------------------- 1 | # ember/no-arrow-function-computed-properties 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Arrow 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. 8 | 9 | ## Rule Details 10 | 11 | This rule disallows using arrow functions in computed properties. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | import EmberObject, { computed } from '@ember/object'; 19 | 20 | const Person = EmberObject.extend({ 21 | fullName: computed('firstName', 'lastName', () => { 22 | return `${this.firstName} ${this.lastName}`; 23 | }) 24 | }); 25 | ``` 26 | 27 | Examples of **correct** code for this rule: 28 | 29 | ```js 30 | import EmberObject, { computed } from '@ember/object'; 31 | 32 | const Person = EmberObject.extend({ 33 | fullName: computed('firstName', 'lastName', function () { 34 | return `${this.firstName} ${this.lastName}`; 35 | }) 36 | }); 37 | ``` 38 | 39 | ## Configuration 40 | 41 | 42 | 43 | | Name | Description | Type | Default | 44 | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------- | :------ | :------ | 45 | | `onlyThisContexts` | Whether the rule should allow or disallow computed properties where the arrow function body does not contain a `this` reference. | Boolean | `false` | 46 | 47 | 48 | 49 | ## References 50 | 51 | - [Arrow function spec](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) 52 | - [Computed property spec](https://api.emberjs.com/ember/release/classes/ComputedProperty) 53 | -------------------------------------------------------------------------------- /docs/rules/no-attrs-in-components.md: -------------------------------------------------------------------------------- 1 | # ember/no-attrs-in-components 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Do not use `this.attrs`. 8 | 9 | In 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`. 10 | 11 | In 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | const name = this.attrs.name; 19 | ``` 20 | 21 | Examples of **correct** code for this rule: 22 | 23 | ```js 24 | // Prefix private properties with _ 25 | const name = this._name; 26 | ``` 27 | 28 | ## Further Reading 29 | 30 | - 31 | -------------------------------------------------------------------------------- /docs/rules/no-capital-letters-in-routes.md: -------------------------------------------------------------------------------- 1 | # ember/no-capital-letters-in-routes 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Raise an error when there is a route with upper-cased letters in router.js. 8 | 9 | When 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. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | this.route('Home'); 17 | this.route('SignUp'); 18 | ``` 19 | 20 | Examples of **correct** code for this rule: 21 | 22 | ```js 23 | this.route('home'); 24 | this.route('sign-up'); 25 | ``` 26 | 27 | ## References 28 | 29 | - [Ember Routing Guide](https://guides.emberjs.com/release/routing/) 30 | 31 | ## Related Rules 32 | 33 | - [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md) 34 | - [route-path-style](route-path-style.md) 35 | - [routes-segments-snake-case](routes-segments-snake-case.md) 36 | -------------------------------------------------------------------------------- /docs/rules/no-classic-components.md: -------------------------------------------------------------------------------- 1 | # ember/no-classic-components 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | This rule aims to enforce Glimmer components instead of classic ones. We should migrate to Glimmer components because 8 | they have few advantages: 9 | 10 | - Simpler API 11 | - No wrapper element 12 | - Namespaced Arguments 13 | - Less lifecycle hooks 14 | - Stateless Template-Only Components 15 | - Unidirectional Dataflow 16 | 17 | With that simpler API we could improve the DX and also lower the entry level for Ember. 18 | 19 | ## Rule Details 20 | 21 | If you want to migrate to Glimmer components this rule can help find the classic components that you need to migrate. 22 | 23 | ## Examples 24 | 25 | Examples of **incorrect** code for this rule: 26 | 27 | ```js 28 | import Component from '@ember/component'; 29 | ``` 30 | 31 | Examples of **correct** code for this rule: 32 | 33 | ```js 34 | import Component from '@glimmer/component'; 35 | ``` 36 | 37 | ## References 38 | 39 | - [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) 40 | - [Ember Glimmer Components RFC](https://github.com/emberjs/rfcs/blob/master/text/0416-glimmer-components.md) 41 | - [Ember Octane Release Plan](https://blog.emberjs.com/2019/08/15/octane-release-plan.html) 42 | - [Glimmer Components Explained](https://www.pzuraq.com/coming-soon-in-ember-octane-part-5-glimmer-components/) 43 | -------------------------------------------------------------------------------- /docs/rules/no-controllers.md: -------------------------------------------------------------------------------- 1 | # ember/no-controllers 2 | 3 | 4 | 5 | Some 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. 6 | 7 | This rule disallows controller usage, except when the controller is used to define `queryParams` (a feature currently only available in controllers). 8 | 9 | Note: this rule will not be added to the `recommended` configuration until controller usage has become less common / deprecated. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | import Controller from '@ember/controller'; 17 | 18 | export default class ArticlesController extends Controller { 19 | // Controller disallowed since it doesn't use `queryParams`. 20 | // ... 21 | } 22 | ``` 23 | 24 | Examples of **correct** code for this rule: 25 | 26 | ```js 27 | import Controller from '@ember/controller'; 28 | 29 | export default class ArticlesController extends Controller { 30 | queryParams = ['category']; // Controller allowed for defining `queryParams`. 31 | @tracked category = null; 32 | // ... 33 | } 34 | ``` 35 | 36 | ## Further Reading 37 | 38 | - [Guide on controllers](https://guides.emberjs.com/release/routing/controllers/) 39 | - [Guide on `queryParameters`](https://guides.emberjs.com/release/routing/query-params/) 40 | -------------------------------------------------------------------------------- /docs/rules/no-current-route-name.md: -------------------------------------------------------------------------------- 1 | # ember/no-current-route-name 2 | 3 | 4 | 5 | The route name is something which is not visible to the user, so it can be 6 | considered an implementation detail. The URL however is visible to the user, so 7 | when writing tests it makes much more sense to assert against `currentURL()` 8 | instead of `currentRouteName()`. 9 | 10 | ## Rule Details 11 | 12 | This rule warns about any usage of the `currentRouteName()` test helper. 13 | 14 | ## Examples 15 | 16 | Examples of **incorrect** code for this rule: 17 | 18 | ```js 19 | assert.equal(currentRouteName(), 'foo'); 20 | ``` 21 | 22 | Examples of **correct** code for this rule: 23 | 24 | ```js 25 | assert.equal(currentURL(), '/foo'); 26 | ``` 27 | 28 | ## Migration 29 | 30 | - Replace `currentRouteName()` with `currentURL()` and adjust 31 | the assertion expectations 32 | 33 | ## References 34 | 35 | - [currentRouteName()](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#currentroutename) documentation 36 | - [currentURL()](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#currenturl) documentation 37 | -------------------------------------------------------------------------------- /docs/rules/no-deeply-nested-dependent-keys-with-each.md: -------------------------------------------------------------------------------- 1 | # ember/no-deeply-nested-dependent-keys-with-each 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Disallows usage of deeply-nested computed property dependent keys with `@each`. 8 | 9 | For performance / complexity reasons, Ember does not allow deeply-nested computed property dependent keys with `@each`. At runtime, it will show a warning about this: 10 | 11 | > 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | export default Component.extend({ 19 | displayNames: computed('todos.@each.owner.name', function () { 20 | return this.todos.map((todo) => todo.owner.name); 21 | }) 22 | }); 23 | ``` 24 | 25 | Examples of **correct** code for this rule: 26 | 27 | ```js 28 | export default Component.extend({ 29 | displayNames: computed('owners.@each.name', function () { 30 | return this.owners.mapBy('name'); 31 | }), 32 | 33 | owners: computed('todos.@each.owner', function () { 34 | return this.todos.mapBy('owner'); 35 | }) 36 | }); 37 | ``` 38 | 39 | ## Further Reading 40 | 41 | - See the [documentation](https://guides.emberjs.com/release/object-model/computed-properties-and-aggregate-data/) for Ember computed properties with `@each` 42 | -------------------------------------------------------------------------------- /docs/rules/no-duplicate-dependent-keys.md: -------------------------------------------------------------------------------- 1 | # ember/no-duplicate-dependent-keys 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Disallow repeating dependent keys. 10 | 11 | ## Rule Details 12 | 13 | This rule makes it easy to spot repeating dependent keys in computed properties. 14 | 15 | ## Examples 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```js 20 | computed('foo.bar', 'foo.baz', 'foo.qux', 'foo.bar', function () { 21 | // ... 22 | }); 23 | // or using brace expansions 24 | computed('foo.{bar,baz,qux}', 'foo.bar', function () { 25 | // ... 26 | }); 27 | ``` 28 | 29 | Examples of **correct** code for this rule: 30 | 31 | ```js 32 | computed('foo.bar', 'foo.baz', 'foo.qux', function () { 33 | // ... 34 | }); 35 | // or using brace expansions 36 | computed('foo.{bar,baz,qux}', 'bar.foo', function () { 37 | // ... 38 | }); 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/rules/no-ember-super-in-es-classes.md: -------------------------------------------------------------------------------- 1 | # ember/no-ember-super-in-es-classes 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | `this._super` is not allowed in ES class methods. 10 | 11 | While `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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | import Component from '@ember/component'; 19 | 20 | export default class MyComponent extends Component { 21 | init(...args) { 22 | this._super(...args); 23 | // Other logic 24 | } 25 | } 26 | ``` 27 | 28 | Examples of **correct** code for this rule: 29 | 30 | ```js 31 | import Component from '@ember/component'; 32 | 33 | export default class MyComponent extends Component { 34 | init(...args) { 35 | super.init(...args); 36 | // Other logic 37 | } 38 | } 39 | ``` 40 | 41 | ```js 42 | import Component from '@ember/component'; 43 | 44 | export default Component.extend({ 45 | init(...args) { 46 | this._super(...args); 47 | // Other logic 48 | } 49 | }); 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/rules/no-ember-testing-in-module-scope.md: -------------------------------------------------------------------------------- 1 | # ember/no-ember-testing-in-module-scope 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | `Ember.testing` is not allowed in modules scope. 8 | 9 | Since ember-cli-qunit@4.1.0 / ember-qunit@3.0.0, `Ember.testing` is only set to 10 | true while a test is executing instead of all the time. Also, `Ember.testing` is a 11 | getter/setter in Ember and destructuring will only read its value at the time 12 | of destructuring. 13 | 14 | 15 | 16 | ## Examples 17 | 18 | Examples of **incorrect** code for this rule: 19 | 20 | ```js 21 | import Component from '@ember/component'; 22 | 23 | export default Component.extend({ 24 | init(...args) { 25 | this._super(...args); 26 | this.isTesting = Ember.testing; 27 | } 28 | }); 29 | ``` 30 | 31 | ```js 32 | import Ember from 'ember'; 33 | import Component from '@ember/component'; 34 | 35 | export default Component.extend({ 36 | isTesting: Ember.testing 37 | }); 38 | ``` 39 | 40 | ```js 41 | import Ember from 'ember'; 42 | 43 | const IS_TESTING = Ember.testing; 44 | ``` 45 | 46 | ```js 47 | import Ember from 'ember'; 48 | 49 | const { testing } = Ember; 50 | ``` 51 | 52 | Examples of **correct** code for this rule: 53 | 54 | ```js 55 | import Ember from 'ember'; 56 | import Component from '@ember/component'; 57 | 58 | export default Component.extend({ 59 | someMethod() { 60 | if (Ember.testing) { 61 | doSomething(); 62 | } else { 63 | doSomethingElse(); 64 | } 65 | } 66 | }); 67 | ``` 68 | 69 | ```js 70 | import Ember from 'ember'; 71 | import Service from '@ember/service'; 72 | 73 | export default Service.extend({ 74 | foo() { 75 | _bar(Ember.testing ? 0 : 400); 76 | } 77 | }); 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/rules/no-empty-attrs.md: -------------------------------------------------------------------------------- 1 | # ember/no-empty-attrs 2 | 3 | 4 | 5 | Be explicit with Ember data attribute types. 6 | 7 | Ember 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. 8 | 9 | Note: this rule is not in the `recommended` configuration because the Ember Data team recommends not using transforms unless you actually want to transform something. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | const { Model, attr } = DS; 17 | 18 | export default Model.extend({ 19 | name: attr(), 20 | points: attr(), 21 | dob: attr() 22 | }); 23 | ``` 24 | 25 | Examples of **correct** code for this rule: 26 | 27 | ```js 28 | const { Model, attr } = DS; 29 | 30 | export default Model.extend({ 31 | name: attr('string'), 32 | points: attr('number'), 33 | dob: attr('date') 34 | }); 35 | ``` 36 | 37 | In case you need a custom behavior, it's good to write your own [transform](https://api.emberjs.com/ember-data/release/classes/Transform). 38 | 39 | ## Help Wanted 40 | 41 | | Issue | Link | 42 | | :-- | :-- | 43 | | ❌ Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) | 44 | -------------------------------------------------------------------------------- /docs/rules/no-function-prototype-extensions.md: -------------------------------------------------------------------------------- 1 | # ember/no-function-prototype-extensions 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | By 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. 8 | 9 | The prototype extensions for the `function` object were deprecated in [RFC #272](https://rfcs.emberjs.com/id/0272-deprecation-native-function-prototype-extensions). 10 | 11 | Use computed property syntax, observer syntax, or module hooks instead of `.property()`, `.observes()` or `.on()` in Ember modules. 12 | 13 | ## Rule Details 14 | 15 | This rule will disallow method calls that match any of the forbidden `function` prototype extension method names. 16 | 17 | ## Examples 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```js 22 | export default Component.extend({ 23 | abc: function () { 24 | /* custom logic */ 25 | }.property('xyz'), 26 | def: function () { 27 | /* custom logic */ 28 | }.observes('xyz'), 29 | ghi: function () { 30 | /* custom logic */ 31 | }.on('didInsertElement') 32 | }); 33 | ``` 34 | 35 | Examples of **correct** code for this rule: 36 | 37 | ```js 38 | export default Component.extend({ 39 | abc: computed('xyz', function () { 40 | /* custom logic */ 41 | }), 42 | def: observer('xyz', function () { 43 | /* custom logic */ 44 | }), 45 | didInsertElement() { 46 | /* custom logic */ 47 | } 48 | }); 49 | ``` 50 | 51 | ## References 52 | 53 | - [Ember prototype extensions documentation](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/) 54 | - [Ember function prototype extensions deprecation RFC](https://rfcs.emberjs.com/id/0272-deprecation-native-function-prototype-extensions) 55 | 56 | ## Related Rules 57 | 58 | - [no-array-prototype-extensions](no-array-prototype-extensions.md) 59 | - [no-string-prototype-extensions](no-string-prototype-extensions.md) 60 | -------------------------------------------------------------------------------- /docs/rules/no-global-jquery.md: -------------------------------------------------------------------------------- 1 | # ember/no-global-jquery 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Do not use global `$` or `jQuery`. 8 | 9 | ## Rule Details 10 | 11 | In 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | export default Component.extend({ 19 | init(...args) { 20 | this._super(...args); 21 | $('.foo').addClass('bar'); // global usage 22 | } 23 | }); 24 | ``` 25 | 26 | Examples of **correct** code for this rule: 27 | 28 | ```js 29 | import Ember from 'ember'; 30 | 31 | const { $ } = Ember; 32 | export default Component.extend({ 33 | init(...args) { 34 | this._super(...args); 35 | Ember.$('.foo').addClass('bar'); // usage from Ember object 36 | // or even better 37 | $('.foo').addClass('bar'); // deconstruction from Ember object 38 | } 39 | }); 40 | ``` 41 | 42 | ## Related 43 | 44 | - [jquery-ember-run](./jquery-ember-run.md) 45 | - [no-jquery](./no-jquery.md) 46 | -------------------------------------------------------------------------------- /docs/rules/no-implicit-service-injection-argument.md: -------------------------------------------------------------------------------- 1 | # ember/no-implicit-service-injection-argument 2 | 3 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 4 | 5 | 6 | 7 | This 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`). 8 | 9 | Some 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. 10 | 11 | Note: 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | import Component from '@ember/component'; 19 | import { inject as service } from '@ember/service'; 20 | 21 | export default class Page extends Component { 22 | @service() serviceName; 23 | } 24 | ``` 25 | 26 | Examples of **correct** code for this rule: 27 | 28 | ```js 29 | import Component from '@ember/component'; 30 | import { inject as service } from '@ember/service'; 31 | 32 | export default class Page extends Component { 33 | @service('service-name') serviceName; 34 | } 35 | ``` 36 | 37 | ## Related Rules 38 | 39 | - [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 40 | 41 | ## References 42 | 43 | - Ember [Services](https://guides.emberjs.com/release/applications/services/) guide 44 | - Ember [inject](https://api.emberjs.com/ember/release/functions/@ember%2Fservice/inject) function spec 45 | -------------------------------------------------------------------------------- /docs/rules/no-incorrect-calls-with-inline-anonymous-functions.md: -------------------------------------------------------------------------------- 1 | # ember/no-incorrect-calls-with-inline-anonymous-functions 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | The following functions keep track of the function references they have been passed: 8 | 9 | - `debounce` 10 | - `once` 11 | - `scheduleOnce` 12 | 13 | To 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. 14 | 15 | ## Rule Details 16 | 17 | This rule disallows using inline anonymous functions with the `debounce`, `once`, and `scheduleOnce` methods when imported from `@ember/runloop`. 18 | 19 | ## Examples 20 | 21 | Examples of **incorrect** code for this rule: 22 | 23 | ```js 24 | import { scheduleOnce, once, debounce } from '@ember/runloop'; 25 | 26 | export default Component.extend({ 27 | didInsertElement() { 28 | this.doWork(); 29 | this.doWork(); 30 | }, 31 | doWork() { 32 | debounce(() => { 33 | /* this will run twice */ 34 | }, 300); 35 | once(() => { 36 | /* this will run twice */ 37 | }); 38 | scheduleOnce('afterRender', function () { 39 | /* this will run twice */ 40 | }); 41 | } 42 | }); 43 | ``` 44 | 45 | Examples of **correct** code for this rule: 46 | 47 | ```js 48 | import { scheduleOnce } from '@ember/runloop'; 49 | 50 | export default Component.extend({ 51 | didInsertElement() { 52 | this.doWork(); 53 | this.doWork(); 54 | }, 55 | doWork() { 56 | scheduleOnce('afterRender', this, this.deferredWork); 57 | }, 58 | deferredWork() { 59 | /* this will only run once */ 60 | } 61 | }); 62 | ``` 63 | 64 | ## References 65 | 66 | - [Ember debounce API Docs](https://api.emberjs.com/ember/release/functions/@ember%2Frunloop/debounce) 67 | - [Ember once API Docs](https://api.emberjs.com/ember/release/functions/@ember%2Frunloop/once) 68 | - [Ember scheduleOnce API Docs](https://api.emberjs.com/ember/release/functions/@ember%2Frunloop/scheduleOnce) 69 | -------------------------------------------------------------------------------- /docs/rules/no-incorrect-computed-macros.md: -------------------------------------------------------------------------------- 1 | # ember/no-incorrect-computed-macros 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | This rule attempts to find incorrect usages of computed property macros, such as calling them with the incorrect number of arguments. 10 | 11 | It 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | import { and, or } from '@ember/object/computed'; 19 | 20 | export default Component.extend({ 21 | macroPropertyAnd: and('someProperty'), // Not enough arguments. 22 | 23 | macroPropertyOr: or('someProperty') // Not enough arguments. 24 | }); 25 | ``` 26 | 27 | Examples of **correct** code for this rule: 28 | 29 | ```js 30 | import { and, or, readOnly } from '@ember/object/computed'; 31 | 32 | export default Component.extend({ 33 | macroPropertyReadOnly: readOnly('someProperty'), 34 | 35 | macroPropertyAnd: and('someProperty1', 'someProperty2'), 36 | 37 | macroPropertyOr: or('someProperty1', 'someProperty2') 38 | }); 39 | ``` 40 | 41 | ## Related Rules 42 | 43 | - [require-computed-macros](require-computed-macros.md) 44 | 45 | ## References 46 | 47 | - [Guide](https://guides.emberjs.com/release/object-model/computed-properties/) for computed properties 48 | - [Spec](https://api.emberjs.com/ember/release/modules/@ember%2Fobject#functions-computed) for computed property macros 49 | -------------------------------------------------------------------------------- /docs/rules/no-invalid-test-waiters.md: -------------------------------------------------------------------------------- 1 | # ember/no-invalid-test-waiters 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Prevents invalid usage of test waiters. 8 | 9 | ## Rule Details 10 | 11 | The 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: 12 | 13 | - Used in module scope 14 | - Assigned to a variable 15 | 16 | ## Examples 17 | 18 | Examples of **incorrect** code for this rule: 19 | 20 | ```js 21 | import { buildWaiter } from 'ember-test-waiters'; 22 | 23 | function useWaiter() { 24 | const myOtherWaiter = buildWaiter('the second'); // inside function 25 | } 26 | ``` 27 | 28 | ```js 29 | import { buildWaiter } from 'ember-test-waiters'; 30 | 31 | buildWaiter('the second'); // not stored in variable 32 | ``` 33 | 34 | Examples of **correct** code for this rule: 35 | 36 | ```js 37 | import { buildWaiter } from 'ember-test-waiters'; 38 | 39 | const myWaiter = buildWaiter('waiterName'); 40 | ``` 41 | 42 | ## References 43 | 44 | For more information on the new test waiters API, please visit [ember-test-waiters](https://github.com/emberjs/ember-test-waiters). 45 | -------------------------------------------------------------------------------- /docs/rules/no-jquery.md: -------------------------------------------------------------------------------- 1 | # ember/no-jquery 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | This rule attempts to catch and prevent any usage of jQuery. 8 | 9 | ## Rule Details 10 | 11 | If you want to remove jQuery, this rule can help you by warning you of any usage of jQuery in your app. 12 | 13 | That includes: 14 | 15 | - `this.$`, either on components or tests. 16 | - `import $ from 'jquery';`; 17 | - The global `$` 18 | - `Ember.$` or `const { $ } = Ember;` 19 | 20 | ## Examples 21 | 22 | Examples of **incorrect** code for this rule: 23 | 24 | ```js 25 | export default Component.extend({ 26 | didInsertElement() { 27 | this.$('input').focus(); 28 | } 29 | }); 30 | ``` 31 | 32 | ```js 33 | export default Component.extend({ 34 | click() { 35 | $('body').addClass('expanded'); 36 | // or 37 | Ember.$('body').addClass('expanded'); 38 | } 39 | }); 40 | ``` 41 | 42 | Examples of **correct** code for this rule: 43 | 44 | ```js 45 | export default Component.extend({ 46 | didInsertElement() { 47 | this.element.querySelector('input').focus(); 48 | } 49 | }); 50 | ``` 51 | 52 | ```js 53 | export default Component.extend({ 54 | click() { 55 | document.body.classList.add('expanded'); 56 | } 57 | }); 58 | ``` 59 | 60 | ## Migration 61 | 62 | For replacing `this.$` on components, you can use the native DOM counterpart `this.element`. 63 | 64 | For replacing `this.$` on tests, check [ember-native-dom-helpers](https://github.com/cibernox/ember-native-dom-helpers). 65 | 66 | Codemods that could help: 67 | 68 | - [ember-3x-codemods](https://github.com/ember-codemods/ember-3x-codemods) 69 | - [ember-test-helpers-codemod](https://github.com/ember-codemods/ember-test-helpers-codemod) 70 | 71 | ## RFCs 72 | 73 | - [Make jQuery optional](https://github.com/emberjs/rfcs/blob/master/text/0294-optional-jquery.md) 74 | - [Remove jQuery by default](https://github.com/emberjs/rfcs/blob/master/text/0386-remove-jquery.md) 75 | 76 | ## Related 77 | 78 | - [jquery-ember-run](./jquery-ember-run.md) 79 | - [no-global-jquery](./no-global-jquery.md) 80 | -------------------------------------------------------------------------------- /docs/rules/no-new-mixins.md: -------------------------------------------------------------------------------- 1 | # ember/no-new-mixins 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Using 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. 8 | 9 | For practical strategies on removing mixins see [this discourse thread](https://discuss.emberjs.com/t/best-way-to-replace-mixins/17395/2). 10 | 11 | For more details and examples of how mixins create problems down-the-line, see these excellent blog posts: 12 | 13 | - [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html) 14 | - [Why Are Mixins Considered Harmful?](https://raganwald.com/2016/07/16/why-are-mixins-considered-harmful.html) 15 | 16 | ## Examples 17 | 18 | Examples of **incorrect** code for this rule: 19 | 20 | ```js 21 | // my-mixin.js 22 | export default Mixin.create({ 23 | isValidClassName(classname) { 24 | return Boolean(className.match('-class')); 25 | }, 26 | 27 | hideModal(value) { 28 | this.set('isHidden', value); 29 | } 30 | }); 31 | ``` 32 | 33 | ```js 34 | // my-component.js 35 | import myMixin from 'my-mixin'; 36 | 37 | export default Component.extend(myMixin, { 38 | aComputedProperty: computed('obj', function () { 39 | return this.isValidClassName(obj.className); 40 | }) 41 | }); 42 | ``` 43 | 44 | Examples of **correct** code for this rule: 45 | 46 | ```js 47 | // my-utils.js 48 | export function isValidClassName(classname) { 49 | return Boolean(className.match('-class')); 50 | } 51 | 52 | export function hideModal(obj, value) { 53 | set(obj, 'isHidden', value); 54 | } 55 | ``` 56 | 57 | ```js 58 | // my-component.js 59 | import { isValidClassName } from 'my-utils'; 60 | 61 | export default Component.extend({ 62 | aComputedProperty: computed('obj', function () { 63 | return isValidClassName(obj.className); 64 | }) 65 | }); 66 | ``` 67 | 68 | ## Related Rules 69 | 70 | - [no-mixins](no-mixins.md) 71 | -------------------------------------------------------------------------------- /docs/rules/no-noop-setup-on-error-in-before.md: -------------------------------------------------------------------------------- 1 | # ember/no-noop-setup-on-error-in-before 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Disallows use of no-op `setupOnerror` in `before`/`beforeEach` since it could mask errors or rejections in tests unintentionally 10 | 11 | ## Rule Details 12 | 13 | This 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. 14 | 15 | ## Examples 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```js 20 | import { setupOnerror } from '@ember/test-helpers'; 21 | import { module } from 'qunit'; 22 | 23 | module('foo', function (hooks) { 24 | hooks.beforeEach(function () { 25 | setupOnerror(() => {}); 26 | }); 27 | }); 28 | ``` 29 | 30 | ```js 31 | import { setupOnerror } from '@ember/test-helpers'; 32 | import { module } from 'qunit'; 33 | 34 | module('foo', function (hooks) { 35 | hooks.before(function () { 36 | setupOnerror(() => {}); 37 | }); 38 | }); 39 | ``` 40 | 41 | Examples of **correct** code for this rule: 42 | 43 | ```js 44 | import { setupOnerror } from '@ember/test-helpers'; 45 | import { module, test } from 'qunit'; 46 | 47 | module('foo', function (hooks) { 48 | test('something', function () { 49 | setupOnerror((error) => { 50 | assert.equal(error.message, 'test', 'Should have message'); 51 | }); 52 | }); 53 | }); 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/rules/no-old-shims.md: -------------------------------------------------------------------------------- 1 | # ember/no-old-shims 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Don't use import paths from `ember-cli-shims`. 10 | 11 | The import paths in `ember-cli-shims` were never considered public API and 12 | were recently replaced by [RFC #176](https://github.com/emberjs/rfcs/pull/176). 13 | If you use `ember-cli-babel` with version `6.6.0` or above you can start using 14 | the "New Module Imports" instead of the old shims or the `Ember` global directly. 15 | This will enable us to build better tree shaking feature into Ember CLI. 16 | 17 | ## Examples 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```js 22 | import Component from 'ember-component'; 23 | import EmberObject from 'ember-object'; 24 | import computed from 'ember-computed'; 25 | import Service from 'ember-service'; 26 | import inject from 'ember-service/inject'; 27 | ``` 28 | 29 | Examples of **correct** code for this rule: 30 | 31 | ```js 32 | import Component from '@ember/component'; 33 | import EmberObject, { computed } from '@ember/object'; 34 | import Service, { inject } from '@ember/service'; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/no-on-calls-in-components.md: -------------------------------------------------------------------------------- 1 | # ember/no-on-calls-in-components 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Prevents using `.on()` in favour of component's lifecycle hooks. 8 | 9 | The order of execution for `on()` is not deterministic. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | export default Component.extend({ 17 | abc: on('didInsertElement', function () { 18 | /* custom logic */ 19 | }) 20 | }); 21 | ``` 22 | 23 | Examples of **correct** code for this rule: 24 | 25 | ```js 26 | export default Component.extend({ 27 | didInsertElement() { 28 | /* custom logic */ 29 | } 30 | }); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/rules/no-pause-test.md: -------------------------------------------------------------------------------- 1 | # ember/no-pause-test 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Disallow use of `pauseTest` helper in tests. 8 | 9 | When `pauseTest()` is committed and run in CI it can cause runners to hang which is undesirable. 10 | 11 | ## Rule Details 12 | 13 | This rule aims to prevent `pauseTest()` from being committed and run in CI. 14 | 15 | ## Examples 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```js 20 | import { module, test } from 'qunit'; 21 | import { setupApplicationTest } from 'ember-qunit'; 22 | 23 | import { pauseTest } from '@ember/test-helpers'; 24 | 25 | module('Acceptance | foo test', function (hooks) { 26 | setupApplicationTest(hooks); 27 | 28 | test('it hangs', async function () { 29 | await this.pauseTest(); 30 | // or 31 | await pauseTest(); 32 | }); 33 | }); 34 | ``` 35 | 36 | ## When Not To Use It 37 | 38 | If you have tests that call `resumeTest()` following a `pauseTest()` 39 | 40 | ```js 41 | import { module, test } from 'qunit'; 42 | import { setupApplicationTest } from 'ember-qunit'; 43 | 44 | import { pauseTest, resumeTest } from '@ember/test-helpers'; 45 | 46 | module('Acceptance | foo test', function (hooks) { 47 | setupApplicationTest(hooks); 48 | 49 | test('it runs', function () { 50 | const promise = pauseTest(); 51 | 52 | // Do some stuff 53 | 54 | resumeTest(); // Done 55 | }); 56 | }); 57 | ``` 58 | 59 | ## Further Reading 60 | 61 | - [ember-test-helpers pauseTest documentation](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#pausetest) 62 | -------------------------------------------------------------------------------- /docs/rules/no-proxies.md: -------------------------------------------------------------------------------- 1 | # ember/no-proxies 2 | 3 | 4 | 5 | You may want to disallow the use of Ember proxy objects (`ObjectProxy`, `ArrayProxy`) in your application for a number of reasons: 6 | 7 | 1. Proxies are relatively rare compared to direct property access on objects. 8 | 2. In part due to their rarity, proxies are not as widely understood. 9 | 3. Proxies can add unnecessary complexity. 10 | 4. 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()`) 11 | 12 | Note: this rule is not in the `recommended` configuration because there are legitimate usages of proxies. 13 | 14 | ## Rule Details 15 | 16 | This rule disallows using Ember proxy objects (`ObjectProxy`, `ArrayProxy`). 17 | 18 | ## Examples 19 | 20 | Examples of **incorrect** code for this rule: 21 | 22 | ```js 23 | import ObjectProxy from '@ember/object/proxy'; 24 | ``` 25 | 26 | ```js 27 | import ArrayProxy from '@ember/array/proxy'; 28 | ``` 29 | 30 | ## Related Rules 31 | 32 | - [no-get](no-get.md) which may need to be disabled for `this.get()` usages in proxy objects 33 | 34 | ## References 35 | 36 | - [ObjectProxy](https://api.emberjs.com/ember/release/classes/ObjectProxy) spec 37 | - [ArrayProxy](https://api.emberjs.com/ember/release/classes/ArrayProxy) spec 38 | -------------------------------------------------------------------------------- /docs/rules/no-replace-test-comments.md: -------------------------------------------------------------------------------- 1 | # ember/no-replace-test-comments 2 | 3 | 4 | 5 | Ember developers using blueprints to generate classes should write real tests. 6 | 7 | Leaving the default test comment in place is a sign of a rushed class. 8 | 9 | Note: this rule will not be added to the `recommended` configuration because it would cause the default ember-cli blueprint to contain lint violations. 10 | 11 | ## Rule Details 12 | 13 | This rule aims to nudge developers into writing more/better tests. 14 | 15 | It aims to do this by complaining at them early about a default test file. 16 | 17 | This rule only fires for test files (ending in '-test.js' or '-test.ts') 18 | 19 | This will especially push TDD (test-driven development) on repos with githooks that enforce lint early. 20 | 21 | ## Examples 22 | 23 | Examples of **incorrect** code for this rule: 24 | 25 | ```js 26 | // Replace this with your real tests. 27 | test('it exists', function (assert) { 28 | const service = this.owner.lookup('service:company'); 29 | assert.ok(service); 30 | }); 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```js 36 | test('it has a purpose beyond mere existence', function (assert) { 37 | const service = this.owner.lookup('service:company'); 38 | set(service, 'company', myCompanyMock); 39 | assert.equal(service.isOnboardingComplete, true, 'the computed property works as expected'); 40 | }); 41 | ``` 42 | 43 | ## Migration 44 | 45 | - The [testing guides](https://guides.emberjs.com/release/testing/testing-components/) shall be your guide 46 | - Consider a code coverage tool like [ember-cli-code-coverage](https://github.com/kategengler/ember-cli-code-coverage) as well 47 | 48 | ## References 49 | 50 | - [Learn Test Driven Development in Ember](https://learntdd.in/ember/) 51 | -------------------------------------------------------------------------------- /docs/rules/no-shadow-route-definition.md: -------------------------------------------------------------------------------- 1 | # ember/no-shadow-route-definition 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Enforce no route path definition shadowing in Router. 8 | 9 | ## Rule Details 10 | 11 | This 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). 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | this.route('main', { path: '/' }, function () { 19 | this.route('nested'); 20 | }); 21 | this.route('nested'); 22 | ``` 23 | 24 | In this example from Router perspective both `nested` routes are on URL `/nested`. 25 | -------------------------------------------------------------------------------- /docs/rules/no-side-effects.md: -------------------------------------------------------------------------------- 1 | # ember/no-side-effects 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | When using computed properties, do not introduce side effects. Side effects make it much more difficult to reason about the origin of changes. 8 | 9 | This rule currently disallows the following side-effect-causing statements inside computed properties: 10 | 11 | - `this.set('x', 123);` 12 | - `this.setProperties({ x: 123 });` 13 | - `this.x = 123;` 14 | 15 | Note that other side effects like network requests should be avoided as well. 16 | 17 | ## Examples 18 | 19 | ```js 20 | import Component from '@ember/component'; 21 | import { alias, filterBy } from '@ember/object/computed'; 22 | 23 | export default Component.extend({ 24 | init(...args) { 25 | this.users = [ 26 | { name: 'Foo', age: 15 }, 27 | { name: 'Bar', age: 16 }, 28 | { name: 'Baz', age: 15 } 29 | ]; 30 | this._super(...args); 31 | }, 32 | 33 | // GOOD: 34 | fifteenGood: filterBy('users', 'age', 15), 35 | fifteenAmountGood: alias('fifteen.length'), 36 | 37 | // BAD: 38 | fifteenAmountBad: 0, 39 | fifteenBad: computed('users', function () { 40 | const fifteen = this.users.filterBy('items', 'age', 15); 41 | this.set('fifteenAmountBad', fifteen.length); // SIDE EFFECT! 42 | return fifteen; 43 | }) 44 | }); 45 | ``` 46 | 47 | ## Configuration 48 | 49 | 50 | 51 | | Name | Description | Type | Default | 52 | | :------------------ | :--------------------------------------------------------------------------------------------- | :------ | :------ | 53 | | `catchEvents` | Whether the rule should catch function calls that send actions or events. | Boolean | `true` | 54 | | `checkPlainGetters` | Whether the rule should check plain (non-computed) getters in native classes for side effects. | Boolean | `true` | 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs/rules/no-string-prototype-extensions.md: -------------------------------------------------------------------------------- 1 | # ember/no-string-prototype-extensions 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | By 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. 8 | 9 | The prototype extensions for the `String` object were deprecated in [RFC #236](https://rfcs.emberjs.com/id/0236-deprecation-ember-string/). 10 | 11 | ## Rule Details 12 | 13 | This rule will disallow method calls that match any of the forbidden `String` prototype extension method names. 14 | 15 | ## Examples 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```js 20 | 'myString'.dasherize(); 21 | ``` 22 | 23 | ```js 24 | someString.capitalize(); 25 | ``` 26 | 27 | ```js 28 | 'foo'.htmlSafe(); 29 | ``` 30 | 31 | Examples of **correct** code for this rule: 32 | 33 | ```js 34 | dasherize('myString'); 35 | ``` 36 | 37 | ```js 38 | capitalize(someString); 39 | ``` 40 | 41 | ```js 42 | htmlSafe('foo'); 43 | ``` 44 | 45 | ## Migration 46 | 47 | Replace e.g.: 48 | 49 | ```js 50 | 'myString'.dasherize(); 51 | ``` 52 | 53 | with: 54 | 55 | ```js 56 | import { dasherize } from '@ember/string'; 57 | 58 | dasherize('myString'); 59 | ``` 60 | 61 | ## References 62 | 63 | - [Ember prototype extensions documentation](https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/) 64 | - [Ember String prototype extensions deprecation RFC](https://rfcs.emberjs.com/id/0236-deprecation-ember-string/) 65 | 66 | ## Related Rules 67 | 68 | - [no-array-prototype-extensions](no-array-prototype-extensions.md) 69 | - [no-function-prototype-extensions](no-function-prototype-extensions.md) 70 | -------------------------------------------------------------------------------- /docs/rules/no-test-and-then.md: -------------------------------------------------------------------------------- 1 | # ember/no-test-and-then 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Use `await` instead of `andThen` test wait helper. 8 | 9 | It'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. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | test('behaves correctly', function (assert) { 17 | click('.button'); 18 | andThen(() => { 19 | assert.ok(this.myAction.calledOnce); 20 | }); 21 | }); 22 | ``` 23 | 24 | Examples of **correct** code for this rule: 25 | 26 | ```js 27 | test('behaves correctly', async function (assert) { 28 | await click('.button'); 29 | assert.ok(this.myAction.calledOnce); 30 | }); 31 | ``` 32 | 33 | ## Migration 34 | 35 | - [async-await-codemod](https://github.com/sgilroy/async-await-codemod) can help convert async function calls / promise chains to use `await` 36 | - [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 37 | -------------------------------------------------------------------------------- /docs/rules/no-test-module-for.md: -------------------------------------------------------------------------------- 1 | # ember/no-test-module-for 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Use `module` instead of `moduleFor`. 8 | 9 | `moduleForComponent`, `moduleFor`, `moduleForAcceptance`, etc have been deprecated and there are codemods to help migrate. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | import { moduleFor } from 'ember-qunit'; 17 | 18 | moduleFor('Test Name'); 19 | ``` 20 | 21 | Examples of **correct** code for this rule: 22 | 23 | ```js 24 | import { module } from 'qunit'; 25 | 26 | module('Test Name', function (hooks) { 27 | // ... 28 | }); 29 | ``` 30 | 31 | ## Migration 32 | 33 | A short guide for how each of the legacy APIs converts to the new APIs: 34 | 35 | - `moduleFor`, `moduleForModel` 36 | 37 | ```js 38 | import { module, test } from 'qunit'; 39 | import { setupTest } from 'ember-qunit'; 40 | 41 | module('...', function (hooks) { 42 | setupTest(hooks); 43 | }); 44 | ``` 45 | 46 | - `moduleForComponent` 47 | 48 | ```js 49 | import { module, test } from 'qunit'; 50 | import { setupRenderingTest } from 'ember-qunit'; 51 | 52 | module('...', function (hooks) { 53 | setupRenderingTest(hooks); 54 | }); 55 | ``` 56 | 57 | - `moduleForAcceptance` 58 | 59 | ```js 60 | import { module, test } from 'qunit'; 61 | import { setupApplicationTest } from 'ember-qunit'; 62 | 63 | module('...', function (hooks) { 64 | setupApplicationTest(hooks); 65 | }); 66 | ``` 67 | 68 | ## References 69 | 70 | - [moduleFor* deprecation notice from ember-qunit 4.5.0](https://github.com/emberjs/ember-qunit/blob/master/CHANGELOG.md#rocket-enhancement-1) 71 | 72 | - Codemod for automated upgrade of tests: [ember-qunit-codemod](https://github.com/ember-codemods/ember-qunit-codemod) 73 | -------------------------------------------------------------------------------- /docs/rules/no-test-support-import.md: -------------------------------------------------------------------------------- 1 | # ember/no-test-support-import 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | No importing of test support files into non-test code.. 8 | 9 | **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. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | > app/routes/index.js 16 | 17 | ```js 18 | import doSomething from '../test-support/some-other-test'; 19 | 20 | import Route from '@ember/routing/route'; 21 | import { action } from '@ember/object'; 22 | 23 | export default class SomeRouteRoute extends Route { 24 | // … 25 | model() { 26 | return doSomething(); 27 | } 28 | } 29 | ``` 30 | 31 | Examples of **correct** code for this rule: 32 | 33 | > tests/unit/foo-test.js 34 | 35 | ```js 36 | import setupModule from '../test-support/setup-module'; 37 | import { module, test } from 'qunit'; 38 | 39 | module('Acceptance | module', setupModule()); 40 | ``` 41 | 42 | > addon-test-support/setupApplication.js 43 | 44 | ```js 45 | import setupModule from '../test-support/setup-module'; 46 | 47 | export default function setupApplicationTest(hooks) { 48 | setupModule(hooks); 49 | // ... 50 | } 51 | ``` 52 | 53 | This 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/`. 54 | 55 | ## Related Rules 56 | 57 | - [no-test-import-export](no-test-import-export.md) 58 | -------------------------------------------------------------------------------- /docs/rules/no-test-this-render.md: -------------------------------------------------------------------------------- 1 | # ember/no-test-this-render 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Ember'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. 8 | 9 | ## Rule Details 10 | 11 | The rule invites users to call `@ember/test-helpers`' `render`/`clearRender` method instead of `this.render`/`this.clearRender` in tests. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | test('baz', function (assert) { 19 | this.render(); 20 | }); 21 | ``` 22 | 23 | ```js 24 | test('baz', function (assert) { 25 | this.clearRender(); 26 | }); 27 | ``` 28 | 29 | Examples of **correct** code for this rule: 30 | 31 | ```js 32 | import { render } from '@ember/test-helpers'; 33 | 34 | test('baz', function (assert) { 35 | render(); 36 | }); 37 | ``` 38 | 39 | ```js 40 | import { clearRender } from '@ember/test-helpers'; 41 | 42 | test('baz', function (assert) { 43 | clearRender(); 44 | }); 45 | ``` 46 | 47 | ## References 48 | 49 | - [Rendering Helpers on `@ember/test-helpers`](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#rendering-helpers). 50 | -------------------------------------------------------------------------------- /docs/rules/no-tracked-properties-from-args.md: -------------------------------------------------------------------------------- 1 | # ember/no-tracked-properties-from-args 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Disallow creation of @tracked properties from args. 8 | 9 | ## Rule Details 10 | 11 | This 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. 12 | If 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. 13 | 14 | ## Examples 15 | 16 | Examples of **incorrect** code for this rule: 17 | 18 | ```js 19 | class Example { 20 | @tracked someValue = this.args.someValue; 21 | } 22 | ``` 23 | 24 | Examples of **correct** code for this rule: 25 | 26 | ```js 27 | class Example { 28 | get someValue() { 29 | return this.args.someValue; 30 | } 31 | } 32 | ``` 33 | 34 | ## References 35 | 36 | - [Glimmer Components - args](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/#toc_getting-used-to-glimmer-components) guide 37 | - [tracked](https://guides.emberjs.com/release/in-depth-topics/autotracking-in-depth/) guide 38 | -------------------------------------------------------------------------------- /docs/rules/no-try-invoke.md: -------------------------------------------------------------------------------- 1 | # ember/no-try-invoke 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | This rule will catch and prevent the use of `tryInvoke`. 8 | 9 | ## Rule Details 10 | 11 | This 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | import { tryInvoke } from '@ember/utils'; 19 | 20 | class FooComponent extends Component { 21 | foo() { 22 | tryInvoke(this.args, 'bar', ['baz']); 23 | } 24 | } 25 | ``` 26 | 27 | Examples of **correct** code for this rule: 28 | 29 | ```js 30 | class FooComponent extends Component { 31 | foo() { 32 | this.args.bar?.('baz'); 33 | } 34 | } 35 | ``` 36 | 37 | ## References 38 | 39 | - [RFC](https://github.com/emberjs/rfcs/pull/673) to deprecate `tryInvoke` 40 | - [spec](https://api.emberjs.com/ember/release/functions/@ember%2Futils/tryInvoke) 41 | -------------------------------------------------------------------------------- /docs/rules/no-unnecessary-index-route.md: -------------------------------------------------------------------------------- 1 | # ember/no-unnecessary-index-route 2 | 3 | 4 | 5 | Disallow unnecessary `index` route definition. 6 | 7 | The `index` route (for the `/` path) is automatically provided at every level of nesting and does not need to be defined in the router. 8 | 9 | ## Examples 10 | 11 | Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | this.route('index'); 15 | ``` 16 | 17 | ```js 18 | this.route('index', { path: '/' }); 19 | ``` 20 | 21 | Examples of **correct** code for this rule: 22 | 23 | ```js 24 | this.route('blog-posts'); 25 | ``` 26 | 27 | ## References 28 | 29 | - [Ember Routing Guide](https://guides.emberjs.com/release/routing/) 30 | 31 | ## Related Rules 32 | 33 | - [no-capital-letters-in-routes](no-capital-letters-in-routes.md) 34 | - [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md) 35 | - [routes-segments-snake-case](routes-segments-snake-case.md) 36 | -------------------------------------------------------------------------------- /docs/rules/no-unnecessary-route-path-option.md: -------------------------------------------------------------------------------- 1 | # ember/no-unnecessary-route-path-option 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Disallow unnecessary route `path` option. 10 | 11 | When defining a route, it's not necessary to specify the `path` option if it matches the route name. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | this.route('blog-posts', { path: '/blog-posts' }); 19 | ``` 20 | 21 | Examples of **correct** code for this rule: 22 | 23 | ```js 24 | this.route('blog-posts'); 25 | ``` 26 | 27 | ```js 28 | this.route('blog-posts', { path: '/blog' }); 29 | ``` 30 | 31 | ## References 32 | 33 | - [Ember Routing Guide](https://guides.emberjs.com/release/routing/) 34 | 35 | ## Related Rules 36 | 37 | - [no-capital-letters-in-routes](no-capital-letters-in-routes.md) 38 | - [no-unnecessary-index-route](no-unnecessary-index-route.md) 39 | - [route-path-style](route-path-style.md) 40 | - [routes-segments-snake-case](routes-segments-snake-case.md) 41 | -------------------------------------------------------------------------------- /docs/rules/no-unnecessary-service-injection-argument.md: -------------------------------------------------------------------------------- 1 | # ember/no-unnecessary-service-injection-argument 2 | 3 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 4 | 5 | 6 | 7 | Disallow unnecessary argument when injecting service. 8 | 9 | It's not necessary to specify an injected service's name as an argument when the property name matches the service name. 10 | 11 | Note: 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. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | import Component from '@ember/component'; 19 | import { inject as service } from '@ember/service'; 20 | 21 | export default Component.extend({ 22 | myServiceName: service('myServiceName') 23 | }); 24 | ``` 25 | 26 | Examples of **correct** code for this rule: 27 | 28 | ```js 29 | export default Component.extend({ 30 | myServiceName: service() 31 | }); 32 | ``` 33 | 34 | ```js 35 | export default Component.extend({ 36 | myServiceName: service('my-service-name') 37 | }); 38 | ``` 39 | 40 | ```js 41 | export default Component.extend({ 42 | otherSpecialName: service('my-service-name') 43 | }); 44 | ``` 45 | 46 | ## Related Rules 47 | 48 | - [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 49 | 50 | ## References 51 | 52 | - Ember [Services](https://guides.emberjs.com/release/applications/services/) guide 53 | - Ember [inject](https://api.emberjs.com/ember/release/functions/@ember%2Fservice/inject) function spec 54 | -------------------------------------------------------------------------------- /docs/rules/no-unused-services.md: -------------------------------------------------------------------------------- 1 | # ember/no-unused-services 2 | 3 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 4 | 5 | 6 | 7 | Disallow unused service injections. 8 | 9 | By removing unused service injections, we can reduce the amount of code we have and improve code readability. 10 | 11 | **Warning**: This rule can exhibit false positives when an injected service is only used in: 12 | 13 | - The corresponding handlebars template file for a controller or component 14 | - A mixin or parent class that the current class extends from 15 | - A child class that extends from the current class 16 | 17 | Given 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. 18 | 19 | ## Examples 20 | 21 | Examples of **incorrect** code for this rule: 22 | 23 | ```js 24 | import Component from '@glimmer/component'; 25 | 26 | export default class MyComponent extends Component { 27 | @service() myService; 28 | 29 | // myService is not referenced below at all 30 | } 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```js 36 | import Component from '@glimmer/component'; 37 | 38 | export default class MyComponent extends Component { 39 | @service() myService; 40 | 41 | get someProperty() { 42 | return this.myService.getSomething(); // using the injected service 43 | } 44 | } 45 | ``` 46 | 47 | ## References 48 | 49 | - Ember [Services](https://guides.emberjs.com/release/applications/services/) guide 50 | - Ember [inject](https://api.emberjs.com/ember/release/functions/@ember%2Fservice/inject) function spec 51 | -------------------------------------------------------------------------------- /docs/rules/no-volatile-computed-properties.md: -------------------------------------------------------------------------------- 1 | # ember/no-volatile-computed-properties 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Volatile computed properties are deprecated as of Ember 3.9. 8 | 9 | ## Rule Details 10 | 11 | This rule disallows using volatile computed properties. 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | const Person = EmberObject.extend({ 19 | fullName: computed(function () { 20 | return `${this.firstName} ${this.lastName}`; 21 | }).volatile() 22 | }); 23 | ``` 24 | 25 | Examples of **correct** code for this rule: 26 | 27 | ```js 28 | const Person = EmberObject.extend({ 29 | // Native getter: 30 | get fullName() { 31 | return `${this.firstName} ${this.lastName}`; 32 | } 33 | }); 34 | ``` 35 | 36 | ## References 37 | 38 | - [Deprecation RFC](https://github.com/emberjs/rfcs/blob/master/text/0370-deprecate-computed-volatile.md) 39 | - [Deprecation list](https://deprecations.emberjs.com/v3.x/#toc_computed-property-volatile) 40 | - [Volatile spec](https://api.emberjs.com/ember/release/classes/ComputedProperty/methods/volatile?anchor=volatile) 41 | - [Computed property spec](https://api.emberjs.com/ember/release/classes/ComputedProperty) 42 | -------------------------------------------------------------------------------- /docs/rules/prefer-ember-test-helpers.md: -------------------------------------------------------------------------------- 1 | # ember/prefer-ember-test-helpers 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | This rule ensures the correct Ember test helper is imported when using methods that have a native window counterpart. 8 | 9 | There are currently 3 Ember test helper methods that have a native window counterpart: 10 | 11 | - blur 12 | - find 13 | - focus 14 | 15 | If 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. 16 | 17 | ## Examples 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```js 22 | test('foo', async (assert) => { 23 | await blur('.some-element'); 24 | }); 25 | ``` 26 | 27 | ```js 28 | test('foo', async (assert) => { 29 | await find('.some-element'); 30 | }); 31 | ``` 32 | 33 | ```js 34 | test('foo', async (assert) => { 35 | await focus('.some-element'); 36 | }); 37 | ``` 38 | 39 | Examples of **correct** code for this rule: 40 | 41 | ```js 42 | import { blur } from '@ember/test-helpers'; 43 | 44 | test('foo', async (assert) => { 45 | await blur('.some-element'); 46 | }); 47 | ``` 48 | 49 | ```js 50 | import { find } from '@ember/test-helpers'; 51 | 52 | test('foo', async (assert) => { 53 | await find('.some-element'); 54 | }); 55 | ``` 56 | 57 | ```js 58 | import { focus } from '@ember/test-helpers'; 59 | 60 | test('foo', async (assert) => { 61 | await focus('.some-element'); 62 | }); 63 | ``` 64 | 65 | ## References 66 | 67 | - [Web API Window Methods](https://developer.mozilla.org/en-US/docs/Web/API/Window#Methods) 68 | - [Ember Test Helpers API Methods](https://github.com/emberjs/ember-test-helpers/blob/master/API.md) 69 | -------------------------------------------------------------------------------- /docs/rules/require-async-inverse-relationship.md: -------------------------------------------------------------------------------- 1 | # ember/require-async-inverse-relationship 2 | 3 | 4 | 5 | This rule ensures that the `async` and `inverse` properties are specified in `@belongsTo` and `@hasMany` decorators in Ember Data models. 6 | 7 | ## Rule Details 8 | 9 | This rule disallows: 10 | 11 | - Using `@belongsTo` without specifying the `async` and `inverse` properties. 12 | - Using `@hasMany` without specifying the `async` and `inverse` properties. 13 | 14 | ## Examples 15 | 16 | Examples of **incorrect** code for this rule: 17 | 18 | ```js 19 | import Model, { belongsTo, hasMany } from '@ember-data/model'; 20 | 21 | export default class FolderModel extends Model { 22 | @hasMany('folder', { inverse: 'parent' }) children; 23 | @belongsTo('folder', { inverse: 'children' }) parent; 24 | } 25 | ``` 26 | 27 | Examples of **correct** code for this rule: 28 | 29 | ```js 30 | import Model, { belongsTo, hasMany } from '@ember-data/model'; 31 | 32 | export default class FolderModel extends Model { 33 | @hasMany('folder', { async: true, inverse: 'parent' }) children; 34 | @belongsTo('folder', { async: true, inverse: 'children' }) parent; 35 | } 36 | ``` 37 | 38 | ## References 39 | 40 | - [Deprecate Non Strict Relationships](https://deprecations.emberjs.com/id/ember-data-deprecate-non-strict-relationships) 41 | - [Ember Data Relationships](https://guides.emberjs.com/release/models/relationships) 42 | -------------------------------------------------------------------------------- /docs/rules/require-fetch-import.md: -------------------------------------------------------------------------------- 1 | # ember/require-fetch-import 2 | 3 | 4 | 5 | Using `fetch()` without importing it causes the browser to use the native, 6 | non-wrapped `window.fetch()`. This is generally fine, but makes testing harder 7 | because this non-wrapped version does not have a built-in test waiter. Because 8 | of this it is generally better to use [ember-fetch] and explicitly 9 | `import fetch from 'fetch'`. 10 | 11 | ## Rule Details 12 | 13 | The rule looks for `fetch()` calls and reports them as issues if no 14 | corresponding import declaration is found. 15 | 16 | ## Examples 17 | 18 | Examples of **incorrect** code for this rule: 19 | 20 | ```js 21 | const result = fetch('/something'); 22 | ``` 23 | 24 | Examples of **correct** code for this rule: 25 | 26 | ```js 27 | import fetch from 'fetch'; 28 | 29 | const result = fetch('/something'); 30 | ``` 31 | 32 | ## Migration 33 | 34 | - Add `import fetch from 'fetch';` to all files that need it 35 | 36 | ## References 37 | 38 | - [@ember/test-waiters](https://github.com/emberjs/ember-test-waiters) addon 39 | 40 | [ember-fetch]: https://github.com/ember-cli/ember-fetch/ 41 | -------------------------------------------------------------------------------- /docs/rules/route-path-style.md: -------------------------------------------------------------------------------- 1 | # ember/route-path-style 2 | 3 | 💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). 4 | 5 | 6 | 7 | Enforces usage of kebab-case (instead of snake_case or camelCase) in route paths. 8 | 9 | A 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. 10 | 11 | Example kebab-case URL: `https://guides.emberjs.com/release/getting-started/core-concepts/` 12 | 13 | ## Examples 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```js 18 | this.route('blog_posts'); 19 | ``` 20 | 21 | ```js 22 | this.route('blogPosts'); 23 | ``` 24 | 25 | ```js 26 | this.route('blog-posts', { path: '/blog_posts' }); 27 | ``` 28 | 29 | ```js 30 | this.route('blog-posts', { path: '/blogPosts' }); 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```js 36 | this.route('blog-posts'); 37 | ``` 38 | 39 | ```js 40 | this.route('blog_posts', { path: '/blog-posts' }); 41 | ``` 42 | 43 | ## References 44 | 45 | - [Ember Routing Guide](https://guides.emberjs.com/release/routing/) 46 | - [Keep a simple URL structure](https://support.google.com/webmasters/answer/76329) article by Google 47 | 48 | ## Related Rules 49 | 50 | - [no-capital-letters-in-routes](no-capital-letters-in-routes.md) 51 | - [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md) 52 | - [routes-segments-snake-case](routes-segments-snake-case.md) 53 | -------------------------------------------------------------------------------- /docs/rules/routes-segments-snake-case.md: -------------------------------------------------------------------------------- 1 | # ember/routes-segments-snake-case 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | Dynamic segments in routes should use _snake case_, so Ember doesn't have to do extra serialization in order to resolve promises. 8 | 9 | ## Examples 10 | 11 | Examples of **incorrect** code for this rule: 12 | 13 | ```js 14 | this.route('tree', { path: ':treeId' }); 15 | ``` 16 | 17 | Examples of **correct** code for this rule: 18 | 19 | ```js 20 | this.route('tree', { path: ':tree_id' }); 21 | ``` 22 | 23 | ## References 24 | 25 | - [Ember Routing Guide](https://guides.emberjs.com/release/routing/) 26 | 27 | ## Related Rules 28 | 29 | - [no-capital-letters-in-routes](no-capital-letters-in-routes.md) 30 | - [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md) 31 | - [route-path-style](route-path-style.md) 32 | -------------------------------------------------------------------------------- /docs/rules/template-indent.md: -------------------------------------------------------------------------------- 1 | # ember/template-indent 2 | 3 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 4 | 5 | 6 | 7 | ## Rule Details 8 | 9 | Enforce consistent indentation for fcct templates. 10 | 11 | This rule extends the base [eslint indent](https://eslint.org/docs/latest/rules/indent) rule, but only applies the indents to Glimmer Nodes. 12 | 13 | Otherwise, it receives the same options as the original and can run together with the base rule. 14 | 15 | ## Configuration 16 | 17 | 18 | 19 | | Name | Type | Default | 20 | | :--------------- | :------- | :------ | 21 | | `ignoreComments` | Boolean | `false` | 22 | | `ignoredNodes` | String[] | | 23 | 24 | 25 | 26 | ## Examples 27 | 28 | Examples of **incorrect** code for this rule: 29 | 30 | ```gjs 31 | // my-octane-component.gjs 32 | 37 | } 38 | ``` 39 | 40 | Examples of **correct** code for this rule: 41 | 42 | ```gjs 43 | // my-component.gjs 44 | 49 | ``` 50 | 51 | ## References 52 | 53 | - [eslint indent](https://eslint.org/docs/latest/rules/indent) 54 | -------------------------------------------------------------------------------- /docs/rules/template-no-let-reference.md: -------------------------------------------------------------------------------- 1 | # ember/template-no-let-reference 2 | 3 | 💼 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`. 4 | 5 | 6 | 7 | Disallows referencing let/var variables in templates. 8 | 9 | ```js 10 | // app/components/foo.gjs 11 | let foo = 1; 12 | 13 | function increment() { 14 | foo++; 15 | } 16 | 17 | export default ; 18 | ``` 19 | 20 | This 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. 21 | 22 | So, generally speaking, one should avoid referencing let variables from within <template> and instead prefer to use const bindings. 23 | 24 | ## Rule Detail 25 | 26 | Use `const` variables instead of `let`. 27 | 28 | ## Examples 29 | 30 | Examples of **incorrect** code for this rule: 31 | 32 | ```js 33 | let x = 1; 34 | 37 | ``` 38 | 39 | ```js 40 | let Comp = x; // SomeComponent 41 | 44 | ``` 45 | 46 | Examples of **correct** code for this rule: 47 | 48 | ```js 49 | const x = 1; 50 | 53 | ``` 54 | 55 | ```js 56 | const Comp = x; // SomeComponent 57 | 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/rules/use-brace-expansion.md: -------------------------------------------------------------------------------- 1 | # ember/use-brace-expansion 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 6 | 7 | This allows much less redundancy and is easier to read. 8 | 9 | Note that **the dependent keys must be together (without space)** for the brace expansion to work. 10 | 11 | ## Examples 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | export default Component.extend({ 17 | fullName: computed('user.firstName', 'user.lastName', { 18 | // Code 19 | }) 20 | }); 21 | ``` 22 | 23 | Examples of **correct** code for this rule: 24 | 25 | ```js 26 | export default Component.extend({ 27 | fullName: computed('user.{firstName,lastName}', { 28 | // Code 29 | }) 30 | }); 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/rules/use-ember-data-rfc-395-imports.md: -------------------------------------------------------------------------------- 1 | # ember/use-ember-data-rfc-395-imports 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/ember-cli/eslint-plugin-ember#-configurations). 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | Use "Ember Data Packages" from Ember RFC #395. 10 | 11 | The goal of this rule is to ease the migration to the new @ember-data packages. 12 | 13 | ## Context 14 | 15 | ember-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. 16 | 17 | For TypeScript users, imports from `ember-data/types/registries/*` are still allowed since there is currently no equivalent in the new packages. 18 | 19 | ## Examples 20 | 21 | Examples of **incorrect** code for this rule: 22 | 23 | ```js 24 | import Model from 'ember-data/model'; 25 | import attr from 'ember-data/attr'; 26 | ``` 27 | 28 | ```js 29 | import DS from 'ember-data'; 30 | 31 | const { Model } = 'ember-data'; 32 | ``` 33 | 34 | Examples of **correct** code for this rule: 35 | 36 | ```js 37 | import Model, { attr } from '@ember-data/model'; 38 | ``` 39 | 40 | ## How to fix 41 | 42 | This 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. 43 | 44 | Note that [a codemod is also available](https://github.com/ember-codemods/ember-data-codemod) to complete the upgrade. 45 | 46 | ## When Not To Use It 47 | 48 | You don't want to use this rule if you're using `ember-data@<3.11`. 49 | 50 | ## Further Reading 51 | 52 | [Ember Data Packages RFC](https://github.com/emberjs/rfcs/blob/master/text/0395-ember-data-packages.md) 53 | -------------------------------------------------------------------------------- /lib/config-legacy/base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | parserOptions: { 5 | ecmaVersion: 2022, 6 | sourceType: 'module', 7 | }, 8 | 9 | env: { 10 | browser: true, 11 | es2022: true, 12 | }, 13 | 14 | plugins: ['ember'], 15 | 16 | overrides: [ 17 | /** 18 | * We don't want to *always* have the preprocessor active, 19 | * it's only relevant on gjs and gts files to detect if eslint config is correctly setup for this files. 20 | */ 21 | { 22 | files: ['**/*.{gts,gjs}'], 23 | parser: 'ember-eslint-parser', 24 | processor: 'ember/noop', 25 | }, 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /lib/config-legacy/recommended-gjs.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | const gjsRules = require('../recommended-rules-gjs'); 3 | 4 | module.exports = { 5 | ...base, 6 | rules: gjsRules, 7 | }; 8 | -------------------------------------------------------------------------------- /lib/config-legacy/recommended-gts.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | const gtsRules = require('../recommended-rules-gts'); 3 | 4 | module.exports = { 5 | ...base, 6 | rules: gtsRules, 7 | }; 8 | -------------------------------------------------------------------------------- /lib/config-legacy/recommended.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | const rules = require('../recommended-rules'); 3 | 4 | module.exports = { 5 | ...base, 6 | rules, 7 | }; 8 | -------------------------------------------------------------------------------- /lib/config/base.js: -------------------------------------------------------------------------------- 1 | const plugin = require('../index'); 2 | const emberEslintParser = require('ember-eslint-parser'); 3 | 4 | module.exports = [ 5 | { 6 | plugins: { ember: plugin }, 7 | }, 8 | 9 | /** 10 | * We don't want to *always* have the preprocessor active, 11 | * it's only relevant on gjs and gts files to detect if eslint config is correctly setup for this files. 12 | */ 13 | { 14 | files: ['**/*.{gts,gjs}'], 15 | languageOptions: { 16 | parser: emberEslintParser, 17 | }, 18 | processor: 'ember/noop', 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /lib/config/recommended-gjs.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | const gjsRules = require('../recommended-rules-gjs'); 3 | 4 | module.exports = [...base, { rules: gjsRules }]; 5 | -------------------------------------------------------------------------------- /lib/config/recommended-gts.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | const gtsRules = require('../recommended-rules-gts'); 3 | 4 | // see https://github.com/typescript-eslint/typescript-eslint/issues/8607 5 | // https://github.com/typescript-eslint/typescript-eslint/blob/v7.4.0/packages/eslint-plugin/src/configs/eslint-recommended-raw.ts 6 | const tsRecommended = { 7 | 'constructor-super': 'off', // ts(2335) & ts(2377) 8 | 'getter-return': 'off', // ts(2378) 9 | 'no-const-assign': 'off', // ts(2588) 10 | 'no-dupe-args': 'off', // ts(2300) 11 | 'no-dupe-class-members': 'off', // ts(2393) & ts(2300) 12 | 'no-dupe-keys': 'off', // ts(1117) 13 | 'no-func-assign': 'off', // ts(2630) 14 | 'no-import-assign': 'off', // ts(2632) & ts(2540) 15 | 'no-new-symbol': 'off', // ts(7009) 16 | 'no-obj-calls': 'off', // ts(2349) 17 | 'no-redeclare': 'off', // ts(2451) 18 | 'no-setter-return': 'off', // ts(2408) 19 | 'no-this-before-super': 'off', // ts(2376) & ts(17009) 20 | 'no-undef': 'off', // ts(2304) & ts(2552) 21 | 'no-unreachable': 'off', // ts(7027) 22 | 'no-unsafe-negation': 'off', // ts(2365) & ts(2322) & ts(2358) 23 | 'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more 24 | 'prefer-const': 'error', // ts provides better types with const 25 | 'prefer-rest-params': 'error', // ts provides better types with rest args over arguments 26 | 'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply 27 | }; 28 | 29 | module.exports = [...base, { rules: { ...gtsRules, ...tsRecommended } }]; 30 | -------------------------------------------------------------------------------- /lib/config/recommended.js: -------------------------------------------------------------------------------- 1 | const base = require('./base'); 2 | const rules = require('../recommended-rules'); 3 | 4 | module.exports = [...base, { rules }]; 5 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const requireIndex = require('requireindex'); 4 | const noop = require('ember-eslint-parser/noop'); 5 | const pkg = require('../package.json'); // eslint-disable-line import/extensions 6 | 7 | module.exports = { 8 | meta: { 9 | name: pkg.name, 10 | version: pkg.version, 11 | }, 12 | rules: requireIndex(`${__dirname}/rules`), 13 | configs: requireIndex(`${__dirname}/config-legacy`), 14 | utils: { 15 | ember: require('./utils/ember'), 16 | }, 17 | processors: { 18 | // https://eslint.org/docs/developer-guide/working-with-plugins#file-extension-named-processor 19 | noop, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/recommended-rules-gjs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * IMPORTANT! 3 | * This file has been automatically generated. 4 | * In order to update its content based on rules' 5 | * definitions, execute "npm run update" 6 | */ 7 | module.exports = { 8 | "ember/template-no-let-reference": "error" 9 | } -------------------------------------------------------------------------------- /lib/recommended-rules-gts.js: -------------------------------------------------------------------------------- 1 | /* 2 | * IMPORTANT! 3 | * This file has been automatically generated. 4 | * In order to update its content based on rules' 5 | * definitions, execute "npm run update" 6 | */ 7 | module.exports = { 8 | "ember/template-no-let-reference": "error" 9 | } -------------------------------------------------------------------------------- /lib/recommended.mjs: -------------------------------------------------------------------------------- 1 | import emberPlugin from './index.js'; 2 | import baseRules from './recommended-rules.js'; 3 | import gjsRules from './recommended-rules-gjs.js'; 4 | import gtsRules from './recommended-rules-gts.js'; 5 | import emberParser from 'ember-eslint-parser'; 6 | 7 | export const plugin = emberPlugin; 8 | export const parser = emberParser; 9 | 10 | export const base = { 11 | files: ['**/*.{js,ts}'], 12 | plugins: { ember: emberPlugin }, 13 | rules: { 14 | ...baseRules, 15 | }, 16 | }; 17 | 18 | export const gjs = { 19 | plugins: { ember: emberPlugin }, 20 | files: ['**/*.gjs'], 21 | languageOptions: { 22 | parser: emberParser, 23 | parserOptions: { 24 | ecmaFeatures: { modules: true }, 25 | ecmaVersion: 'latest', 26 | // babel config options should be supplied in the consuming project 27 | }, 28 | }, 29 | processor: 'ember/noop', 30 | rules: { 31 | ...base.rules, 32 | ...gjsRules, 33 | }, 34 | }; 35 | 36 | export const gts = { 37 | plugins: { ember: emberPlugin }, 38 | files: ['**/*.gts'], 39 | languageOptions: { 40 | parser: emberParser, 41 | parserOptions: { 42 | extraFileExtensions: ['.gts'], 43 | }, 44 | // parser options should be supplied in the consuming project 45 | }, 46 | processor: 'ember/noop', 47 | rules: { 48 | ...base.rules, 49 | ...gtsRules, 50 | }, 51 | }; 52 | 53 | export default { 54 | // Helpful utility exports 55 | plugin, 56 | parser, 57 | // Recommended config sets 58 | configs: { 59 | base, 60 | gjs, 61 | gts, 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /lib/rules/alias-model-in-controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../utils/utils'); 4 | const ember = require('../utils/ember'); 5 | 6 | //------------------------------------------------------------------------------ 7 | // Controllers - Alias your model 8 | //------------------------------------------------------------------------------ 9 | 10 | /** @type {import('eslint').Rule.RuleModule} */ 11 | module.exports = { 12 | meta: { 13 | type: 'suggestion', 14 | docs: { 15 | description: 'enforce aliasing model in controllers', 16 | category: 'Controllers', 17 | recommended: false, 18 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/alias-model-in-controller.md', 19 | }, 20 | fixable: null, 21 | schema: [], 22 | }, 23 | 24 | create(context) { 25 | const message = 'Alias your model'; 26 | 27 | const report = function (node) { 28 | context.report({ node, message }); 29 | }; 30 | 31 | const sourceCode = context.getSourceCode(); 32 | const { scopeManager } = sourceCode; 33 | 34 | return { 35 | CallExpression(node) { 36 | if (!ember.isEmberController(context, node)) { 37 | return; 38 | } 39 | 40 | const properties = ember.getModuleProperties(node, scopeManager); 41 | let aliasPresent = false; 42 | 43 | for (const property of properties) { 44 | const parsedCallee = utils.parseCallee(property.value); 45 | const parsedArgs = utils.parseArgs(property.value); 46 | 47 | if ( 48 | parsedCallee.length > 0 && 49 | ['alias', 'readOnly', 'reads'].includes(parsedCallee.pop()) && 50 | (parsedArgs[0] === 'model' || String(parsedArgs[0]).startsWith('model.')) 51 | ) { 52 | aliasPresent = true; 53 | } 54 | } 55 | 56 | if (!aliasPresent) { 57 | report(node); 58 | } 59 | }, 60 | }; 61 | }, 62 | }; 63 | -------------------------------------------------------------------------------- /lib/rules/avoid-using-needs-in-controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ember = require('../utils/ember'); 4 | 5 | //------------------------------------------------------------------------------ 6 | // Ember object rule - Avoid using needs in controllers 7 | //------------------------------------------------------------------------------ 8 | 9 | /** @type {import('eslint').Rule.RuleModule} */ 10 | module.exports = { 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: 'disallow using `needs` in controllers', 15 | category: 'Controllers', 16 | recommended: true, 17 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/avoid-using-needs-in-controllers.md', 18 | }, 19 | fixable: null, 20 | schema: [], 21 | }, 22 | 23 | create(context) { 24 | const report = function (node) { 25 | const message = 26 | '`needs` API has been deprecated, `Ember.inject.controller` should be used instead'; 27 | context.report({ node, message }); 28 | }; 29 | 30 | const sourceCode = context.getSourceCode(); 31 | const { scopeManager } = sourceCode; 32 | 33 | return { 34 | CallExpression(node) { 35 | const isReopenNode = ember.isReopenObject(node) || ember.isReopenClassObject(node); 36 | 37 | if (!ember.isEmberController(context, node) && !isReopenNode) { 38 | return; 39 | } 40 | 41 | const properties = ember.getModuleProperties(node, scopeManager); 42 | 43 | for (const property of properties) { 44 | if ( 45 | property.type === 'Property' && 46 | property.key.type === 'Identifier' && 47 | property.key.name === 'needs' 48 | ) { 49 | report(property); 50 | } 51 | } 52 | }, 53 | }; 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /lib/rules/closure-actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | 5 | //------------------------------------------------------------------------------ 6 | // Components - Closure actions 7 | //------------------------------------------------------------------------------ 8 | 9 | const ERROR_MESSAGE = 'Use closure actions'; 10 | 11 | /** @type {import('eslint').Rule.RuleModule} */ 12 | module.exports = { 13 | meta: { 14 | type: 'suggestion', 15 | docs: { 16 | description: 'enforce usage of closure actions', 17 | category: 'Deprecations', 18 | recommended: true, 19 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/closure-actions.md', 20 | }, 21 | fixable: null, 22 | schema: [], 23 | }, 24 | 25 | ERROR_MESSAGE, 26 | 27 | create(context) { 28 | const report = function (node) { 29 | context.report({ node, message: ERROR_MESSAGE }); 30 | }; 31 | 32 | return { 33 | MemberExpression(node) { 34 | const isSendAction = 35 | types.isThisExpression(node.object) && 36 | types.isIdentifier(node.property) && 37 | node.property.name === 'sendAction'; 38 | 39 | if (isSendAction) { 40 | report(node); 41 | } 42 | }, 43 | }; 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /lib/rules/no-actions-hash.js: -------------------------------------------------------------------------------- 1 | const ember = require('../utils/ember'); 2 | const utils = require('../utils/utils'); 3 | 4 | const ERROR_MESSAGE = 'Use the @action decorator instead of declaring an actions hash'; 5 | 6 | /** @type {import('eslint').Rule.RuleModule} */ 7 | module.exports = { 8 | ERROR_MESSAGE, 9 | meta: { 10 | type: 'suggestion', 11 | docs: { 12 | description: 'disallow the actions hash in components, controllers, and routes', 13 | category: 'Ember Octane', 14 | recommended: true, 15 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-actions-hash.md', 16 | }, 17 | fixable: null, 18 | schema: [], 19 | }, 20 | 21 | create: (context) => { 22 | const sourceCode = context.getSourceCode(); 23 | const { scopeManager } = sourceCode; 24 | 25 | function reportActionsProp(properties) { 26 | const actionsProp = properties.find((property) => ember.isActionsProp(property)); 27 | if (actionsProp) { 28 | context.report({ node: actionsProp, message: ERROR_MESSAGE }); 29 | } 30 | } 31 | 32 | return { 33 | ClassDeclaration(node) { 34 | if (inClassWhichCanContainActions(context, node)) { 35 | reportActionsProp([ 36 | ...utils.findNodes(node.body.body, 'ClassProperty'), // ESLint v7 37 | ...utils.findNodes(node.body.body, 'PropertyDefinition'), // ESLint v8 38 | ]); 39 | } 40 | }, 41 | CallExpression(node) { 42 | if (inClassWhichCanContainActions(context, node)) { 43 | reportActionsProp(ember.getModuleProperties(node, scopeManager)); 44 | } 45 | }, 46 | }; 47 | }, 48 | }; 49 | 50 | function inClassWhichCanContainActions(context, node) { 51 | return ( 52 | ember.isEmberComponent(context, node) || 53 | ember.isEmberController(context, node) || 54 | ember.isEmberRoute(context, node) 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /lib/rules/no-at-ember-render-modifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ERROR_MESSAGE = 4 | 'Do not use @ember/render-modifiers. Instead, use derived data patterns, and/or co-locate destruction via @ember/destroyable'; 5 | //------------------------------------------------------------------------------ 6 | // Rule Definition 7 | //------------------------------------------------------------------------------ 8 | 9 | function importDeclarationIsPackageName(node, path) { 10 | return node.source.value === path || node.source.value.startsWith(`${path}/`); 11 | } 12 | 13 | /** @type {import('eslint').Rule.RuleModule} */ 14 | module.exports = { 15 | meta: { 16 | type: 'suggestion', 17 | docs: { 18 | description: 'disallow importing from @ember/render-modifiers', 19 | category: 'Deprecations', 20 | recommended: true, 21 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-at-ember-render-modifiers.md', 22 | }, 23 | fixable: null, 24 | schema: [], 25 | }, 26 | 27 | ERROR_MESSAGE, 28 | 29 | create(context) { 30 | return { 31 | ImportDeclaration(node) { 32 | if (importDeclarationIsPackageName(node, '@ember/render-modifiers')) { 33 | context.report({ 34 | node, 35 | message: ERROR_MESSAGE, 36 | }); 37 | } 38 | }, 39 | }; 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /lib/rules/no-attrs-in-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | const { isEmberComponent, isGlimmerComponent } = require('../utils/ember'); 5 | 6 | const ERROR_MESSAGE = 'Do not use `this.attrs`'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // General rule - Don't use this.attrs 10 | //------------------------------------------------------------------------------ 11 | 12 | /** @type {import('eslint').Rule.RuleModule} */ 13 | module.exports = { 14 | meta: { 15 | type: 'problem', 16 | docs: { 17 | description: 'disallow usage of `this.attrs` in components', 18 | category: 'Components', 19 | recommended: true, 20 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-attrs-in-components.md', 21 | }, 22 | fixable: null, 23 | schema: [], 24 | }, 25 | 26 | ERROR_MESSAGE, 27 | 28 | create(context) { 29 | let currentEmberComponent = null; 30 | 31 | return { 32 | ClassDeclaration(node) { 33 | if (isEmberComponent(context, node) || isGlimmerComponent(context, node)) { 34 | currentEmberComponent = node; 35 | } 36 | }, 37 | 38 | CallExpression(node) { 39 | if (isEmberComponent(context, node)) { 40 | currentEmberComponent = node; 41 | } 42 | }, 43 | 44 | 'ClassDeclaration:exit'(node) { 45 | if (currentEmberComponent === node) { 46 | currentEmberComponent = null; 47 | } 48 | }, 49 | 50 | 'CallExpression:exit'(node) { 51 | if (currentEmberComponent === node) { 52 | currentEmberComponent = null; 53 | } 54 | }, 55 | 56 | MemberExpression(node) { 57 | if (currentEmberComponent && isThisAttrsExpression(node)) { 58 | context.report({ node: node.property, message: ERROR_MESSAGE }); 59 | } 60 | }, 61 | }; 62 | }, 63 | }; 64 | 65 | function isThisAttrsExpression(node) { 66 | return types.isThisExpression(node.object) && node.property.name === 'attrs'; 67 | } 68 | -------------------------------------------------------------------------------- /lib/rules/no-attrs-snapshot.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../utils/utils'); 4 | 5 | //------------------------------------------------------------------------------ 6 | // General rule - Disallow use of attrs snapshot in `didReceiveAttrs` 7 | // and `didUpdateAttrs`. 8 | //------------------------------------------------------------------------------ 9 | 10 | const ERROR_MESSAGE = 11 | 'Do not use the attrs snapshot that is passed in `didReceiveAttrs` and `didUpdateAttrs`.'; 12 | 13 | const hasAttrsSnapShot = function (node) { 14 | const methodName = utils.getPropertyValue(node, 'parent.key.name'); 15 | const hasParams = node.params.length > 0; 16 | 17 | return (methodName === 'didReceiveAttrs' || methodName === 'didUpdateAttrs') && hasParams; 18 | }; 19 | 20 | /** @type {import('eslint').Rule.RuleModule} */ 21 | module.exports = { 22 | meta: { 23 | type: 'problem', 24 | docs: { 25 | description: 26 | 'disallow use of attrs snapshot in the `didReceiveAttrs` and `didUpdateAttrs` component hooks', 27 | category: 'Components', 28 | recommended: true, 29 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-attrs-snapshot.md', 30 | }, 31 | fixable: null, 32 | schema: [], 33 | }, 34 | 35 | ERROR_MESSAGE, 36 | 37 | create(context) { 38 | const report = function (node) { 39 | context.report({ node, message: ERROR_MESSAGE }); 40 | }; 41 | 42 | return { 43 | FunctionExpression(node) { 44 | if (hasAttrsSnapShot(node)) { 45 | report(node); 46 | } 47 | }, 48 | }; 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /lib/rules/no-capital-letters-in-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ember = require('../utils/ember'); 4 | const types = require('../utils/types'); 5 | 6 | //------------------------------------------------------------------------------ 7 | // Routing - No capital letters in routes 8 | //------------------------------------------------------------------------------ 9 | 10 | /** @type {import('eslint').Rule.RuleModule} */ 11 | module.exports = { 12 | meta: { 13 | type: 'problem', 14 | docs: { 15 | description: 'disallow routes with uppercased letters in router.js', 16 | category: 'Routes', 17 | recommended: true, 18 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-capital-letters-in-routes.md', 19 | }, 20 | fixable: null, 21 | schema: [], 22 | }, 23 | create(context) { 24 | const report = function (node) { 25 | context.report({ node, message: "Unexpected capital letter in route's name" }); 26 | }; 27 | 28 | return { 29 | CallExpression(node) { 30 | if (ember.isRoute(node) && node.arguments[0] && types.isLiteral(node.arguments[0])) { 31 | const routeName = node.arguments[0].value; 32 | const hasAnyUppercaseLetter = Boolean(routeName.match('[A-Z]')); 33 | 34 | if (hasAnyUppercaseLetter) { 35 | report(node); 36 | } 37 | } 38 | }, 39 | }; 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /lib/rules/no-classic-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | const assert = require('assert'); 5 | 6 | const ERROR_MESSAGE = 7 | 'Use Glimmer components(@glimmer/component) instead of classic components(@ember/component)'; 8 | 9 | /** @type {import('eslint').Rule.RuleModule} */ 10 | module.exports = { 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: 'enforce using Glimmer components', 15 | category: 'Components', 16 | recommended: true, 17 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-classic-components.md', 18 | }, 19 | fixable: null, 20 | schema: [], 21 | }, 22 | 23 | ERROR_MESSAGE, 24 | 25 | create(context) { 26 | const report = function (node) { 27 | context.report({ node, message: ERROR_MESSAGE }); 28 | }; 29 | 30 | return { 31 | ImportDeclaration(node) { 32 | if (node.source.value === '@ember/component') { 33 | const importDefaultSpecifier = getImportDefaultSpecifier(node); 34 | if (importDefaultSpecifier) { 35 | report(importDefaultSpecifier); 36 | } 37 | } 38 | }, 39 | }; 40 | }, 41 | }; 42 | 43 | function getImportDefaultSpecifier(node) { 44 | assert(types.isImportDeclaration(node)); 45 | return node.specifiers.find((specifier) => types.isImportDefaultSpecifier(specifier)); 46 | } 47 | -------------------------------------------------------------------------------- /lib/rules/no-current-route-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ERROR_MESSAGE = 'Use currentURL() instead of currentRouteName()'; 4 | 5 | /** @type {import('eslint').Rule.RuleModule} */ 6 | module.exports = { 7 | meta: { 8 | type: 'suggestion', 9 | docs: { 10 | description: 'disallow usage of the `currentRouteName()` test helper', 11 | category: 'Testing', 12 | recommended: false, 13 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-current-route-name.md', 14 | }, 15 | fixable: null, 16 | schema: [], 17 | }, 18 | 19 | ERROR_MESSAGE, 20 | 21 | create(context) { 22 | const importAliases = []; 23 | 24 | return { 25 | ImportSpecifier(node) { 26 | const { imported, local } = node; 27 | if ( 28 | imported.type === 'Identifier' && 29 | imported.name === 'currentRouteName' && 30 | local.type === 'Identifier' 31 | ) { 32 | importAliases.push(local.name); 33 | } 34 | }, 35 | 36 | CallExpression(node) { 37 | const { callee } = node; 38 | if (callee.type === 'Identifier' && importAliases.includes(callee.name)) { 39 | context.report({ node, message: ERROR_MESSAGE }); 40 | } 41 | }, 42 | }; 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /lib/rules/no-ember-super-in-es-classes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isDirectlyInClass(node) { 4 | let currentNode = node.parent; 5 | let inFunctionAlready = false; 6 | 7 | while (currentNode) { 8 | if (currentNode.type === 'MethodDefinition') { 9 | return true; 10 | } else if (currentNode.type === 'FunctionExpression') { 11 | if (inFunctionAlready) { 12 | return false; 13 | } else { 14 | inFunctionAlready = true; 15 | } 16 | } 17 | 18 | currentNode = currentNode.parent; 19 | } 20 | 21 | return false; 22 | } 23 | 24 | /** @type {import('eslint').Rule.RuleModule} */ 25 | module.exports = { 26 | meta: { 27 | type: 'problem', 28 | docs: { 29 | description: 'disallow use of `this._super` in ES class methods', 30 | category: 'Ember Octane', 31 | recommended: true, 32 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-ember-super-in-es-classes.md', 33 | }, 34 | fixable: 'code', 35 | schema: [], 36 | }, 37 | 38 | create(context) { 39 | return { 40 | 'MethodDefinition MemberExpression[object.type="ThisExpression"][property.name="_super"]'( 41 | node 42 | ) { 43 | if (!isDirectlyInClass(node)) { 44 | return; 45 | } 46 | 47 | context.report({ 48 | node, 49 | message: "Don't use `this._super` in ES classes; instead, you should use `super`", 50 | fix(fixer) { 51 | let method = node; 52 | while (method.type !== 'MethodDefinition') { 53 | method = method.parent; 54 | } 55 | 56 | if (method.key.type === 'Identifier') { 57 | return fixer.replaceText(node, `super.${method.key.name}`); 58 | } 59 | 60 | const text = context.getSourceCode().getText(method.key); 61 | return fixer.replaceText(node, `super[${text}]`); 62 | }, 63 | }); 64 | }, 65 | }; 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /lib/rules/no-empty-attrs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ember = require('../utils/ember'); 4 | 5 | //------------------------------------------------------------------------------ 6 | // Ember Data - Be explicit with Ember data attribute types 7 | //------------------------------------------------------------------------------ 8 | 9 | /** @type {import('eslint').Rule.RuleModule} */ 10 | module.exports = { 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: 'disallow usage of empty attributes in Ember Data models', 15 | category: 'Ember Data', 16 | recommended: false, 17 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-empty-attrs.md', 18 | }, 19 | fixable: null, 20 | schema: [], 21 | }, 22 | 23 | create(context) { 24 | const message = 'Supply proper attribute type'; 25 | const filePath = context.getFilename(); 26 | 27 | const report = function (node) { 28 | context.report({ node, message }); 29 | }; 30 | 31 | const sourceCode = context.getSourceCode(); 32 | const { scopeManager } = sourceCode; 33 | 34 | return { 35 | CallExpression(node) { 36 | if (!ember.isDSModel(node, filePath)) { 37 | return; 38 | } 39 | 40 | const allProperties = ember.getModuleProperties(node, scopeManager); 41 | const isDSAttr = allProperties.filter((property) => 42 | ember.isModule(property.value, 'attr', 'DS') 43 | ); 44 | 45 | for (const attr of isDSAttr) { 46 | if (attr.value.arguments.length === 0) { 47 | report(attr.value); 48 | } 49 | } 50 | }, 51 | }; 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /lib/rules/no-empty-glimmer-component-classes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { isGlimmerComponent } = require('../utils/ember'); 4 | 5 | const ERROR_MESSAGE = 'Do not create empty backing classes for Glimmer components.'; 6 | const ERROR_MESSAGE_TEMPLATE_TAG = 7 | 'Do not create empty backing classes for Glimmer template tag only components.'; 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | /** @type {import('eslint').Rule.RuleModule} */ 14 | module.exports = { 15 | meta: { 16 | type: 'suggestion', 17 | docs: { 18 | description: 'disallow empty backing classes for Glimmer components', 19 | category: 'Ember Octane', 20 | recommended: true, 21 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-empty-glimmer-component-classes.md', 22 | }, 23 | fixable: null, 24 | schema: [], 25 | }, 26 | 27 | ERROR_MESSAGE, 28 | ERROR_MESSAGE_TEMPLATE_TAG, 29 | 30 | create(context) { 31 | return { 32 | ClassDeclaration(node) { 33 | if (!isGlimmerComponent(context, node)) { 34 | return; 35 | } 36 | 37 | const subClassHasTypeDefinition = node.typeParameters?.params?.length > 0; 38 | if (!node.decorators?.length && node.body.body.length === 0 && !subClassHasTypeDefinition) { 39 | context.report({ node, message: ERROR_MESSAGE }); 40 | } else if ( 41 | node.body.body.length === 1 && 42 | node.body.body[0].type === 'GlimmerTemplate' && 43 | !subClassHasTypeDefinition 44 | ) { 45 | context.report({ node, message: ERROR_MESSAGE_TEMPLATE_TAG }); 46 | } 47 | }, 48 | }; 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /lib/rules/no-function-prototype-extensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | // General rule - Don't use Ember's function prototype extensions like .property() or .observe() 7 | //---------------------------------------------------------------------------------------------- 8 | 9 | /** @type {import('eslint').Rule.RuleModule} */ 10 | module.exports = { 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: "disallow usage of Ember's `function` prototype extensions", 15 | category: 'Deprecations', 16 | recommended: true, 17 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-function-prototype-extensions.md', 18 | }, 19 | fixable: null, 20 | schema: [], 21 | }, 22 | 23 | create(context) { 24 | const message = "Don't use Ember's function prototype extensions"; 25 | 26 | const functionPrototypeExtensionNames = new Set([ 27 | 'property', 28 | 'observes', 29 | 'observesBefore', 30 | 'on', 31 | ]); 32 | 33 | const isFunctionPrototypeExtension = function (property) { 34 | return types.isIdentifier(property) && functionPrototypeExtensionNames.has(property.name); 35 | }; 36 | 37 | const report = function (node) { 38 | context.report({ node, message }); 39 | }; 40 | 41 | return { 42 | CallExpression(node) { 43 | const callee = node.callee; 44 | 45 | if ( 46 | types.isCallExpression(node) && 47 | types.isMemberExpression(callee) && 48 | types.isFunctionExpression(callee.object) && 49 | isFunctionPrototypeExtension(callee.property) 50 | ) { 51 | report(node); 52 | } 53 | }, 54 | }; 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /lib/rules/no-global-jquery.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ReferenceTracker } = require('eslint-utils'); 4 | const { globalMap } = require('../utils/jquery'); 5 | 6 | const ERROR_MESSAGE = 'Do not use global `$` or `jQuery`'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | /** @type {import('eslint').Rule.RuleModule} */ 13 | module.exports = { 14 | meta: { 15 | type: 'suggestion', 16 | docs: { 17 | description: 'disallow usage of global jQuery object', 18 | category: 'jQuery', 19 | recommended: true, 20 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-global-jquery.md', 21 | }, 22 | fixable: null, 23 | schema: [], 24 | }, 25 | 26 | ERROR_MESSAGE, 27 | 28 | create(context) { 29 | return { 30 | Program(node) { 31 | const sourceCode = context.sourceCode ?? context.getSourceCode(); 32 | const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope(); 33 | 34 | const tracker = new ReferenceTracker(scope); 35 | 36 | for (const { node } of tracker.iterateGlobalReferences(globalMap)) { 37 | context.report({ node, message: ERROR_MESSAGE }); 38 | } 39 | }, 40 | }; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /lib/rules/no-html-safe.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ERROR_MESSAGE = 'Do not use `htmlSafe`.'; 4 | const { getImportIdentifier } = require('../utils/import'); 5 | 6 | //------------------------------------------------------------------------------ 7 | // Rule Definition 8 | //------------------------------------------------------------------------------ 9 | 10 | /** @type {import('eslint').Rule.RuleModule} */ 11 | module.exports = { 12 | meta: { 13 | type: 'suggestion', 14 | docs: { 15 | description: 'disallow the use of `htmlSafe`', 16 | category: 'Miscellaneous', 17 | recommended: false, 18 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-html-safe.md', 19 | }, 20 | fixable: null, 21 | schema: [], 22 | }, 23 | 24 | ERROR_MESSAGE, 25 | 26 | create(context) { 27 | let importedHtmlSafeName; 28 | 29 | return { 30 | ImportDeclaration(node) { 31 | const importSource = node.source.value; 32 | 33 | if (importSource === '@ember/string') { 34 | importedHtmlSafeName = 35 | importedHtmlSafeName || getImportIdentifier(node, '@ember/string', 'htmlSafe'); 36 | } else if (importSource === '@ember/template') { 37 | importedHtmlSafeName = 38 | importedHtmlSafeName || getImportIdentifier(node, '@ember/template', 'htmlSafe'); 39 | } 40 | }, 41 | 42 | CallExpression(node) { 43 | if (node.callee.type === 'Identifier' && node.callee.name === importedHtmlSafeName) { 44 | context.report({ 45 | node, 46 | message: ERROR_MESSAGE, 47 | }); 48 | } 49 | }, 50 | }; 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /lib/rules/no-invalid-test-waiters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getImportIdentifier } = require('../utils/import'); 4 | const utils = require('../utils/utils'); 5 | 6 | const MODULE_SCOPE_ERROR_MESSAGE = 'The buildWaiter function should be invoked in module scope.'; 7 | const DIRECT_ASSIGNMENT_ERROR_MESSAGE = 8 | 'The result of the `buildWaiter` function must be a direct assignment to a module scoped variable'; 9 | 10 | function isInModuleScope(node) { 11 | const ancestorVariableDeclaration = utils.getAncestor( 12 | node, 13 | (ancestor) => ancestor.type === 'VariableDeclaration' 14 | ); 15 | 16 | return ancestorVariableDeclaration.parent.type === 'Program'; 17 | } 18 | 19 | function isDirectVariableDeclaration(node) { 20 | return node.parent.type === 'VariableDeclarator'; 21 | } 22 | 23 | /** @type {import('eslint').Rule.RuleModule} */ 24 | module.exports = { 25 | meta: { 26 | type: 'suggestion', 27 | docs: { 28 | description: 'disallow incorrect usage of test waiter APIs', 29 | category: 'Testing', 30 | recommended: true, 31 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-invalid-test-waiters.md', 32 | }, 33 | fixable: null, 34 | schema: [], 35 | }, 36 | 37 | MODULE_SCOPE_ERROR_MESSAGE, 38 | DIRECT_ASSIGNMENT_ERROR_MESSAGE, 39 | 40 | create(context) { 41 | let buildWaiter; 42 | 43 | return { 44 | ImportDeclaration(node) { 45 | if (node.source.value === 'ember-test-waiters') { 46 | buildWaiter = 47 | buildWaiter || getImportIdentifier(node, 'ember-test-waiters', 'buildWaiter'); 48 | } 49 | }, 50 | 51 | CallExpression(node) { 52 | if (node.callee.type !== 'Identifier' || node.callee.name !== buildWaiter) { 53 | return; 54 | } 55 | 56 | if (!isDirectVariableDeclaration(node)) { 57 | context.report({ 58 | node, 59 | message: DIRECT_ASSIGNMENT_ERROR_MESSAGE, 60 | }); 61 | } else if (!isInModuleScope(node)) { 62 | context.report({ 63 | node, 64 | message: MODULE_SCOPE_ERROR_MESSAGE, 65 | }); 66 | } 67 | }, 68 | }; 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /lib/rules/no-legacy-test-waiters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { getImportIdentifier } = require('../utils/import'); 3 | 4 | const LEGACY_TEST_WAITER_FUNCTIONS = ['registerWaiter', 'unregisterWaiter']; 5 | const ERROR_MESSAGE = 6 | '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.'; 7 | 8 | /** @type {import('eslint').Rule.RuleModule} */ 9 | module.exports = { 10 | meta: { 11 | type: 'suggestion', 12 | docs: { 13 | description: 'disallow the use of the legacy test waiter APIs', 14 | category: 'Testing', 15 | recommended: true, 16 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-legacy-test-waiters.md', 17 | }, 18 | fixable: null, 19 | schema: [], 20 | }, 21 | 22 | ERROR_MESSAGE, 23 | 24 | create(context) { 25 | let testWaitersIdentifiers = []; 26 | 27 | return { 28 | ImportDeclaration(node) { 29 | if (node.source.value !== '@ember/test') { 30 | return; 31 | } 32 | 33 | testWaitersIdentifiers = LEGACY_TEST_WAITER_FUNCTIONS.map((fn) => 34 | getImportIdentifier(node, '@ember/test', fn) 35 | ); 36 | }, 37 | 38 | CallExpression(node) { 39 | if ( 40 | node.callee.type === 'Identifier' && 41 | testWaitersIdentifiers.includes(node.callee.name) 42 | ) { 43 | context.report({ node, message: ERROR_MESSAGE }); 44 | } 45 | }, 46 | }; 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /lib/rules/no-mixins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //------------------------------------------------------------------------------ 4 | // General rule - Don't use mixins 5 | //------------------------------------------------------------------------------ 6 | 7 | const ERROR_MESSAGE = "Don't use a mixin"; 8 | const mixinPathRegex = /\/mixins\//; 9 | 10 | /** @type {import('eslint').Rule.RuleModule} */ 11 | module.exports = { 12 | meta: { 13 | type: 'suggestion', 14 | docs: { 15 | description: 'disallow the usage of mixins', 16 | category: 'Deprecations', 17 | recommended: true, 18 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-mixins.md', 19 | }, 20 | fixable: null, 21 | schema: [], 22 | }, 23 | 24 | ERROR_MESSAGE, 25 | 26 | create(context) { 27 | return { 28 | ImportDeclaration(node) { 29 | const importPath = node.source.value; 30 | if (mixinPathRegex.test(importPath)) { 31 | context.report({ node, message: ERROR_MESSAGE }); 32 | } 33 | }, 34 | }; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /lib/rules/no-new-mixins.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ember = require('../utils/ember'); 4 | 5 | //------------------------------------------------------------------------------ 6 | // General rule - Don't create new mixins 7 | //------------------------------------------------------------------------------ 8 | 9 | const ERROR_MESSAGE = "Don't create new mixins"; 10 | 11 | /** @type {import('eslint').Rule.RuleModule} */ 12 | module.exports = { 13 | meta: { 14 | type: 'suggestion', 15 | docs: { 16 | description: 'disallow the creation of new mixins', 17 | category: 'Deprecations', 18 | recommended: true, 19 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-new-mixins.md', 20 | }, 21 | fixable: null, 22 | schema: [], 23 | }, 24 | 25 | ERROR_MESSAGE, 26 | 27 | create(context) { 28 | return { 29 | CallExpression(node) { 30 | if (ember.isEmberMixin(context, node)) { 31 | context.report({ node, message: ERROR_MESSAGE }); 32 | } 33 | }, 34 | 35 | ClassDeclaration(node) { 36 | if (ember.isEmberMixin(context, node)) { 37 | context.report({ node, message: ERROR_MESSAGE }); 38 | } 39 | }, 40 | }; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /lib/rules/no-pause-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emberUtils = require('../utils/ember'); 4 | const types = require('../utils/types'); 5 | const { getImportIdentifier } = require('../utils/import'); 6 | 7 | const ERROR_MESSAGE = 'Do not commit `pauseTest()`'; 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | /** @type {import('eslint').Rule.RuleModule} */ 14 | module.exports = { 15 | meta: { 16 | type: 'suggestion', 17 | docs: { 18 | description: 'disallow usage of the `pauseTest` helper in tests', 19 | category: 'Testing', 20 | recommended: true, 21 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-pause-test.md', 22 | }, 23 | fixable: null, 24 | schema: [], 25 | }, 26 | 27 | ERROR_MESSAGE, 28 | 29 | create(context) { 30 | if (!emberUtils.isTestFile(context.getFilename())) { 31 | return {}; 32 | } 33 | 34 | let importedPauseTestName; 35 | 36 | return { 37 | ImportDeclaration(node) { 38 | if (node.source.value === '@ember/test-helpers') { 39 | importedPauseTestName = 40 | importedPauseTestName || getImportIdentifier(node, '@ember/test-helpers', 'pauseTest'); 41 | } 42 | }, 43 | 44 | CallExpression(node) { 45 | const { callee } = node; 46 | 47 | if ( 48 | (types.isIdentifier(callee) && callee.name === importedPauseTestName) || 49 | (types.isThisExpression(callee.object) && 50 | types.isIdentifier(callee.property) && 51 | callee.property.name === 'pauseTest') 52 | ) { 53 | context.report({ 54 | node, 55 | message: ERROR_MESSAGE, 56 | }); 57 | } 58 | }, 59 | }; 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /lib/rules/no-proxies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ERROR_MESSAGE = 'Do not use array or object proxies.'; 4 | 5 | //------------------------------------------------------------------------------ 6 | // Rule Definition 7 | //------------------------------------------------------------------------------ 8 | 9 | /** @type {import('eslint').Rule.RuleModule} */ 10 | module.exports = { 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: 'disallow using array or object proxies', 15 | category: 'Ember Object', 16 | recommended: false, 17 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-proxies.md', 18 | }, 19 | fixable: null, 20 | schema: [], 21 | }, 22 | 23 | ERROR_MESSAGE, 24 | 25 | create(context) { 26 | return { 27 | ImportDeclaration(node) { 28 | if ( 29 | node.source.value === '@ember/object/proxy' || 30 | node.source.value === '@ember/array/proxy' 31 | ) { 32 | context.report({ node, message: ERROR_MESSAGE }); 33 | } 34 | }, 35 | }; 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /lib/rules/no-replace-test-comments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emberUtils = require('../utils/ember'); 4 | 5 | const ERROR_MESSAGE = "No 'Replace this with your real tests' comments. Add a substantive test."; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | /** @type {import('eslint').Rule.RuleModule} */ 12 | module.exports = { 13 | meta: { 14 | type: 'suggestion', 15 | docs: { 16 | description: "disallow 'Replace this with your real tests' comments in test files", 17 | category: 'Testing', 18 | recommended: false, 19 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-replace-test-comments.md', 20 | }, 21 | fixable: null, 22 | schema: [], 23 | }, 24 | 25 | ERROR_MESSAGE, 26 | 27 | create(context) { 28 | //---------------------------------------------------------------------- 29 | // Helpers 30 | //---------------------------------------------------------------------- 31 | 32 | const checkForRealTestsComment = (node) => { 33 | const { value = '' } = node || {}; 34 | const regex = /replace this with your real tests/i; 35 | const message = ERROR_MESSAGE; 36 | if (regex.test(value)) { 37 | context.report({ node, message }); 38 | } 39 | }; 40 | 41 | //---------------------------------------------------------------------- 42 | // Public 43 | //---------------------------------------------------------------------- 44 | 45 | if (!emberUtils.isTestFile(context.getFilename())) { 46 | return {}; 47 | } 48 | return { 49 | Program(/* node */) { 50 | const sourceCode = context.getSourceCode() || {}; 51 | const comments = sourceCode.getAllComments() || []; 52 | for (const comment of comments) { 53 | checkForRealTestsComment(comment); 54 | } 55 | }, 56 | }; 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /lib/rules/no-string-prototype-extensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { getImportIdentifier } = require('../utils/import'); 4 | 5 | const ERROR_MESSAGE = 'Do not using `String` prototype extension methods.'; 6 | 7 | const EXTENSION_METHODS = new Set([ 8 | 'camelize', 9 | 'capitalize', 10 | 'classify', 11 | 'dasherize', 12 | 'decamelize', 13 | 'htmlSafe', 14 | 'loc', 15 | 'underscore', 16 | 'w', 17 | ]); 18 | 19 | /** @type {import('eslint').Rule.RuleModule} */ 20 | module.exports = { 21 | meta: { 22 | type: 'suggestion', 23 | docs: { 24 | description: 'disallow usage of `String` prototype extensions', 25 | category: 'Deprecations', 26 | recommended: true, 27 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-string-prototype-extensions.md', 28 | }, 29 | fixable: null, 30 | schema: [], 31 | }, 32 | 33 | ERROR_MESSAGE, 34 | 35 | create(context) { 36 | let importedEmberName; 37 | 38 | return { 39 | CallExpression(node) { 40 | const { callee } = node; 41 | if (callee.type !== 'MemberExpression') { 42 | return; 43 | } 44 | 45 | const { object, property } = callee; 46 | if (object.type === 'ThisExpression') { 47 | return; 48 | } 49 | if (property.type !== 'Identifier') { 50 | return; 51 | } 52 | 53 | if ( 54 | object.type === 'MemberExpression' && 55 | object.object.type === 'Identifier' && 56 | object.object.name === importedEmberName && 57 | object.property.type === 'Identifier' && 58 | object.property.name === 'String' 59 | ) { 60 | // Using functions directly from "Ember.String..." is allowed. 61 | return; 62 | } 63 | 64 | if (EXTENSION_METHODS.has(property.name)) { 65 | context.report({ node: property, message: ERROR_MESSAGE }); 66 | } 67 | }, 68 | 69 | ImportDeclaration(node) { 70 | if (node.source.value === 'ember') { 71 | importedEmberName = importedEmberName || getImportIdentifier(node, 'ember'); 72 | } 73 | }, 74 | }; 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /lib/rules/no-test-and-then.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | const emberUtils = require('../utils/ember'); 5 | 6 | const ERROR_MESSAGE = 'Use `await` instead of `andThen` test wait helper.'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | /** @type {import('eslint').Rule.RuleModule} */ 13 | module.exports = { 14 | meta: { 15 | type: 'suggestion', 16 | docs: { 17 | description: 'disallow usage of the `andThen` test wait helper', 18 | category: 'Testing', 19 | recommended: true, 20 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-and-then.md', 21 | }, 22 | fixable: null, 23 | schema: [], 24 | }, 25 | 26 | ERROR_MESSAGE, 27 | 28 | create(context) { 29 | if (!emberUtils.isTestFile(context.getFilename())) { 30 | return {}; 31 | } 32 | 33 | return { 34 | CallExpression(node) { 35 | const callee = node.callee; 36 | if (types.isIdentifier(callee) && callee.name === 'andThen') { 37 | context.report({ 38 | node, 39 | message: ERROR_MESSAGE, 40 | }); 41 | } 42 | }, 43 | }; 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /lib/rules/no-test-support-import.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview no importing of test files and no exporting in test files 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const path = require('path'); 8 | 9 | const ERROR_MESSAGE_NO_IMPORT = 10 | 'Do not import a file from test-support into production code, only into test files.'; 11 | 12 | /** @type {import('eslint').Rule.RuleModule} */ 13 | module.exports = { 14 | ERROR_MESSAGE_NO_IMPORT, 15 | 16 | meta: { 17 | type: 'problem', 18 | docs: { 19 | description: 'disallow importing of "test-support" files in production code.', 20 | category: 'Testing', 21 | recommended: true, 22 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-support-import.md', 23 | }, 24 | schema: [], 25 | }, 26 | 27 | create: function create(context) { 28 | const fileName = context.getFilename(); 29 | const fileNameParts = path.normalize(fileName).split(path.sep); 30 | 31 | return { 32 | ImportDeclaration(node) { 33 | const importSource = node.source.value; 34 | const importSourceParts = path.normalize(importSource).split(path.sep); 35 | 36 | if ( 37 | importSourceParts.includes('test-support') && 38 | !( 39 | fileNameParts.includes('tests') || 40 | fileNameParts.includes('addon-test-support') || 41 | fileNameParts.includes('test-support') 42 | ) 43 | ) { 44 | context.report({ 45 | message: ERROR_MESSAGE_NO_IMPORT, 46 | node, 47 | }); 48 | } 49 | }, 50 | }; 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /lib/rules/no-test-this-render.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emberUtils = require('../utils/ember'); 4 | const types = require('../utils/types'); 5 | 6 | function makeErrorMessage(functionName) { 7 | return `Do not use \`this.${functionName}()\`. Prefer \`${functionName}\` from \`@ember/test-helpers\` instead.`; 8 | } 9 | 10 | //------------------------------------------------------------------------------ 11 | // Rule Definition 12 | //------------------------------------------------------------------------------ 13 | 14 | /** @type {import('eslint').Rule.RuleModule} */ 15 | module.exports = { 16 | meta: { 17 | type: 'suggestion', 18 | docs: { 19 | description: 20 | "disallow usage of the `this.render` in tests, recommending to use @ember/test-helpers' `render` instead.", 21 | category: 'Testing', 22 | recommended: true, 23 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-test-this-render.md', 24 | }, 25 | fixable: null, 26 | schema: [], 27 | }, 28 | 29 | makeErrorMessage, 30 | 31 | create(context) { 32 | if (!emberUtils.isTestFile(context.getFilename())) { 33 | return {}; 34 | } 35 | 36 | return { 37 | CallExpression(node) { 38 | const { callee } = node; 39 | 40 | if ( 41 | types.isThisExpression(callee.object) && 42 | types.isIdentifier(callee.property) && 43 | (callee.property.name === 'render' || callee.property.name === 'clearRender') 44 | ) { 45 | context.report({ 46 | node, 47 | message: makeErrorMessage(callee.property.name), 48 | }); 49 | } 50 | }, 51 | }; 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /lib/rules/no-try-invoke.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | const { getImportIdentifier } = require('../utils/import'); 5 | 6 | const ERROR_MESSAGE = 'Use optional chaining operator `?.()` instead of `tryInvoke`'; 7 | 8 | /** @type {import('eslint').Rule.RuleModule} */ 9 | module.exports = { 10 | ERROR_MESSAGE, 11 | meta: { 12 | type: 'suggestion', 13 | docs: { 14 | description: "disallow usage of the Ember's `tryInvoke` util", 15 | category: 'Ember Object', 16 | recommended: true, 17 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-try-invoke.md', 18 | }, 19 | fixable: null, 20 | schema: [], 21 | }, 22 | 23 | create(context) { 24 | let importedTryInvokeName; 25 | 26 | return { 27 | ImportDeclaration(node) { 28 | const importSource = node.source.value; 29 | 30 | if (importSource === '@ember/utils') { 31 | importedTryInvokeName = 32 | importedTryInvokeName || getImportIdentifier(node, '@ember/utils', 'tryInvoke'); 33 | } 34 | }, 35 | 36 | CallExpression(node) { 37 | if (types.isIdentifier(node.callee) && node.callee.name === importedTryInvokeName) { 38 | context.report({ 39 | node, 40 | message: ERROR_MESSAGE, 41 | }); 42 | } 43 | }, 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /lib/rules/no-unnecessary-index-route.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emberUtils = require('../utils/ember'); 4 | 5 | //------------------------------------------------------------------------------ 6 | // Rule Definition 7 | //------------------------------------------------------------------------------ 8 | 9 | const ERROR_MESSAGE = 10 | 'The `index` route is automatically provided and does not need to be defined.'; 11 | 12 | /** @type {import('eslint').Rule.RuleModule} */ 13 | module.exports = { 14 | meta: { 15 | type: 'suggestion', 16 | docs: { 17 | description: 'disallow unnecessary `index` route definition', 18 | category: 'Routes', 19 | recommended: false, 20 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-unnecessary-index-route.md', 21 | }, 22 | fixable: null, 23 | schema: [], 24 | }, 25 | 26 | ERROR_MESSAGE, 27 | 28 | create(context) { 29 | return { 30 | CallExpression(node) { 31 | if (!emberUtils.isRoute(node)) { 32 | return; 33 | } 34 | 35 | if (node.arguments[0].value !== 'index') { 36 | return; 37 | } 38 | 39 | context.report({ 40 | node, 41 | message: ERROR_MESSAGE, 42 | }); 43 | }, 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /lib/rules/no-volatile-computed-properties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('../utils/types'); 4 | const emberUtils = require('../utils/ember'); 5 | const { getImportIdentifier } = require('../utils/import'); 6 | 7 | const ERROR_MESSAGE = 'Do not use volatile computed properties'; 8 | 9 | /** @type {import('eslint').Rule.RuleModule} */ 10 | module.exports = { 11 | ERROR_MESSAGE, 12 | 13 | meta: { 14 | type: 'suggestion', 15 | docs: { 16 | description: 'disallow volatile computed properties', 17 | category: 'Computed Properties', 18 | recommended: true, 19 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-volatile-computed-properties.md', 20 | }, 21 | schema: [], 22 | }, 23 | 24 | create(context) { 25 | let importedEmberName; 26 | let importedComputedName; 27 | 28 | return { 29 | ImportDeclaration(node) { 30 | if (node.source.value === 'ember') { 31 | importedEmberName = importedEmberName || getImportIdentifier(node, 'ember'); 32 | } 33 | if (node.source.value === '@ember/object') { 34 | importedComputedName = 35 | importedComputedName || getImportIdentifier(node, '@ember/object', 'computed'); 36 | } 37 | }, 38 | 39 | CallExpression(node) { 40 | if ( 41 | types.isMemberExpression(node.callee) && 42 | types.isCallExpression(node.callee.object) && 43 | emberUtils.isComputedProp(node.callee.object, importedEmberName, importedComputedName) && 44 | types.isIdentifier(node.callee.property) && 45 | node.callee.property.name === 'volatile' 46 | ) { 47 | context.report({ node: node.callee.property, message: ERROR_MESSAGE }); 48 | } 49 | }, 50 | }; 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /lib/rules/prefer-ember-test-helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const emberUtils = require('../utils/ember'); 4 | const { ReferenceTracker } = require('eslint-utils'); 5 | 6 | //------------------------------------------------------------------------------------- 7 | // Rule Definition 8 | //------------------------------------------------------------------------------------- 9 | 10 | /** @type {import('eslint').Rule.RuleModule} */ 11 | module.exports = { 12 | meta: { 13 | type: 'suggestion', 14 | docs: { 15 | description: 'enforce usage of `@ember/test-helpers` methods over native window methods', 16 | category: 'Testing', 17 | recommended: true, 18 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/prefer-ember-test-helpers.md', 19 | }, 20 | schema: [], 21 | }, 22 | 23 | create: (context) => { 24 | if (!emberUtils.isTestFile(context.getFilename())) { 25 | return {}; 26 | } 27 | 28 | const showErrorMessage = (node, methodName) => { 29 | context.report({ 30 | data: { methodName }, 31 | message: 'Import the `{{methodName}}()` method from @ember/test-helpers', 32 | node, 33 | }); 34 | }; 35 | 36 | return { 37 | Program(node) { 38 | const sourceCode = context.sourceCode ?? context.getSourceCode(); 39 | const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope(); 40 | 41 | const tracker = new ReferenceTracker(scope); 42 | const traceMap = { 43 | blur: { [ReferenceTracker.CALL]: true }, 44 | find: { [ReferenceTracker.CALL]: true }, 45 | focus: { [ReferenceTracker.CALL]: true }, 46 | }; 47 | 48 | for (const { node } of tracker.iterateGlobalReferences(traceMap)) { 49 | showErrorMessage(node, node.callee.name); 50 | } 51 | }, 52 | }; 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /lib/rules/require-fetch-import.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ReferenceTracker } = require('eslint-utils'); 4 | 5 | const ERROR_MESSAGE = 'Explicitly import `fetch` instead of using `window.fetch`'; 6 | 7 | /** @type {import('eslint').Rule.RuleModule} */ 8 | module.exports = { 9 | meta: { 10 | type: 'suggestion', 11 | docs: { 12 | description: 'enforce explicit import for `fetch()`', 13 | category: 'Miscellaneous', 14 | recommended: false, 15 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/require-fetch-import.md', 16 | }, 17 | fixable: null, 18 | schema: [], 19 | }, 20 | 21 | ERROR_MESSAGE, 22 | 23 | create(context) { 24 | return { 25 | 'Program:exit'(node) { 26 | const sourceCode = context.sourceCode ?? context.getSourceCode(); 27 | const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope(); 28 | 29 | const tracker = new ReferenceTracker(scope); 30 | 31 | const traceMap = { 32 | fetch: { [ReferenceTracker.CALL]: true }, 33 | }; 34 | 35 | for (const { node } of tracker.iterateGlobalReferences(traceMap)) { 36 | context.report({ node, message: ERROR_MESSAGE }); 37 | } 38 | }, 39 | }; 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /lib/rules/template-no-let-reference.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Rule.RuleModule} */ 2 | module.exports = { 3 | meta: { 4 | type: 'suggestion', 5 | docs: { 6 | description: 'disallow referencing let variables in \\', 7 | category: 'Ember Octane', 8 | recommendedGjs: true, 9 | recommendedGts: true, 10 | url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-let-reference.md', 11 | }, 12 | fixable: null, 13 | schema: [], 14 | messages: { 15 | 'no-let': 'update-able variables are not supported in templates, reference a const variable', 16 | }, 17 | }, 18 | 19 | create: (context) => { 20 | const sourceCode = context.sourceCode; 21 | 22 | function checkIfWritableReferences(node, scope) { 23 | const ref = scope.references.find((v) => v.identifier === node); 24 | if (!ref) { 25 | return; 26 | } 27 | if (ref.resolved?.identifiers.some((i) => ['let', 'var'].includes(i.parent.parent.kind))) { 28 | context.report({ node, messageId: 'no-let' }); 29 | } 30 | } 31 | 32 | return { 33 | GlimmerPathExpression(node) { 34 | checkIfWritableReferences(node.head, sourceCode.getScope(node)); 35 | }, 36 | 37 | GlimmerElementNode(node) { 38 | // glimmer element is in its own scope, need to get upper scope 39 | const scope = sourceCode.getScope(node.parent); 40 | // GlimmerElementNode is not referenced, instead use tag name parts[0] 41 | checkIfWritableReferences(node.parts[0], scope); 42 | }, 43 | }; 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /lib/utils/fixer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = require('./types'); 4 | 5 | module.exports = { 6 | insertImportDeclaration, 7 | removeCommaSeparatedNode, 8 | }; 9 | 10 | function removeCommaSeparatedNode(node, sourceCode, fixer) { 11 | const tokenBefore = sourceCode.getTokenBefore(node); 12 | const tokenAfter = sourceCode.getTokenAfter(node); 13 | 14 | const removeComma = types.isCommaToken(tokenAfter) 15 | ? fixer.remove(tokenAfter) 16 | : fixer.remove(tokenBefore); 17 | const removeNode = fixer.remove(node); 18 | 19 | return [removeComma, removeNode]; 20 | } 21 | 22 | /** 23 | * Returns an ESLint fixing object that inserts an import declaration depending on the passed arguments 24 | * 25 | * @param {Object} sourceCode The ESLint source code object 26 | * @param {Object} fixer The ESLint fixer object which will be used to apply fixes. 27 | * @param {String} source The name of the package from where the methods/properties will be imported. 28 | * @param {String} specifier The name of the method/property that needs to be imported. 29 | * @param {String} defaultSpecifier The name of the default specifier that needs to be imported. 30 | * @returns {Object} ESLint fixing object 31 | */ 32 | function insertImportDeclaration(sourceCode, fixer, source, specifier, defaultSpecifier) { 33 | return fixer.insertTextBefore( 34 | sourceCode.ast, 35 | `import ${defaultSpecifier ? `${defaultSpecifier}, ` : ''}{ ${specifier} } from '${source}';\n` 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /lib/utils/javascript.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | duplicateArrays, 5 | removeWhitespace, 6 | }; 7 | 8 | /** 9 | * duplicateArrays([["a", "b"]], 2) -> [["a", "b"], ["a", "b"], ["a", "b"]] 10 | * @param {Array} arr 11 | * @param {number} times 12 | * @returns {Array} 13 | */ 14 | function duplicateArrays(arr, times) { 15 | const result = []; 16 | for (let i = 0; i <= times; i++) { 17 | result.push(...arr.map((a) => a.slice(0))); // eslint-disable-line unicorn/prefer-spread 18 | } 19 | return result; 20 | } 21 | 22 | function removeWhitespace(str) { 23 | // Removes whitespace anywhere inside string. 24 | return str.replaceAll(/\s/g, ''); 25 | } 26 | -------------------------------------------------------------------------------- /lib/utils/jquery-methods.js: -------------------------------------------------------------------------------- 1 | // This file is built by scripts/list-jquery-methods.js; do not edit it directly. 2 | module.exports = [ 3 | 'Animation', 4 | 'Callbacks', 5 | 'Deferred', 6 | 'Event', 7 | 'Tween', 8 | 'ajax', 9 | 'ajaxPrefilter', 10 | 'ajaxSetup', 11 | 'ajaxTransport', 12 | 'attr', 13 | 'camelCase', 14 | 'cleanData', 15 | 'clone', 16 | 'contains', 17 | 'css', 18 | 'data', 19 | 'dequeue', 20 | 'each', 21 | 'error', 22 | 'escapeSelector', 23 | 'extend', 24 | 'filter', 25 | 'find', 26 | 'fx', 27 | 'get', 28 | 'getJSON', 29 | 'getScript', 30 | 'globalEval', 31 | 'grep', 32 | 'hasData', 33 | 'holdReady', 34 | 'htmlPrefilter', 35 | 'inArray', 36 | 'isArray', 37 | 'isEmptyObject', 38 | 'isFunction', 39 | 'isNumeric', 40 | 'isPlainObject', 41 | 'isWindow', 42 | 'isXMLDoc', 43 | 'makeArray', 44 | 'map', 45 | 'merge', 46 | 'noConflict', 47 | 'nodeName', 48 | 'noop', 49 | 'now', 50 | 'param', 51 | 'parseHTML', 52 | 'parseJSON', 53 | 'parseXML', 54 | 'post', 55 | 'prop', 56 | 'proxy', 57 | 'queue', 58 | 'ready', 59 | 'readyException', 60 | 'removeAttr', 61 | 'removeData', 62 | 'removeEvent', 63 | 'speed', 64 | 'style', 65 | 'text', 66 | 'trim', 67 | 'type', 68 | 'unique', 69 | 'uniqueSort', 70 | 'when', 71 | ]; 72 | -------------------------------------------------------------------------------- /lib/utils/jquery.js: -------------------------------------------------------------------------------- 1 | const { ReferenceTracker } = require('eslint-utils'); 2 | const jqueryMethods = require('../utils/jquery-methods'); 3 | 4 | const jqueryMap = { 5 | [ReferenceTracker.CALL]: true, 6 | }; 7 | 8 | for (const method of jqueryMethods) { 9 | jqueryMap[method] = { [ReferenceTracker.CALL]: true }; 10 | } 11 | 12 | /** 13 | * Global references 14 | * 15 | * eg; $(body) and $.post() 16 | * 17 | * For use with ReferenceTracker: tracker.iterateGlobalReferences(); 18 | */ 19 | const globalMap = { 20 | $: jqueryMap, 21 | jQuery: jqueryMap, 22 | }; 23 | 24 | /** 25 | * ESM references 26 | * import $ from 'jquery' 27 | * import { $ as jq } from 'ember' 28 | * 29 | * eg; 30 | * $(body) and jq.post() 31 | * 32 | * For use with ReferenceTracker: tracker.iterateEsmReferences(); 33 | */ 34 | const esmMap = { 35 | jquery: { 36 | [ReferenceTracker.ESM]: true, 37 | default: jqueryMap, 38 | }, 39 | ember: { 40 | [ReferenceTracker.ESM]: true, 41 | default: { 42 | $: jqueryMap, 43 | }, 44 | $: jqueryMap, 45 | }, 46 | }; 47 | 48 | module.exports = { 49 | globalMap, 50 | esmMap, 51 | }; 52 | -------------------------------------------------------------------------------- /lib/utils/property-setter.js: -------------------------------------------------------------------------------- 1 | const types = require('./types'); 2 | 3 | module.exports = { 4 | isThisSet, 5 | }; 6 | 7 | /** 8 | * Checks if an AssignmentExpression looks like `this.x =` or `this.x.y =`. 9 | * 10 | * @param {Node} node The AssignmentExpression node to check. 11 | * @returns {boolean} Whether the node looks as expected. 12 | */ 13 | function isThisSet(node) { 14 | if (!types.isAssignmentExpression(node)) { 15 | return false; 16 | } 17 | 18 | let current = node.left; 19 | if (!types.isMemberExpression(current)) { 20 | return false; 21 | } 22 | while (types.isMemberExpression(current.object)) { 23 | current = current.object; 24 | } 25 | if (!types.isThisExpression(current.object)) { 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/scope-references-this.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const estraverse = require('estraverse'); 4 | 5 | /** 6 | * Determines whether this AST node uses the `this` of the surrounding context. 7 | * 8 | * @param {ASTNode} node 9 | * @returns {boolean} 10 | */ 11 | function scopeReferencesThis(node) { 12 | let result = false; 13 | 14 | estraverse.traverse(node, { 15 | enter(node) { 16 | switch (node.type) { 17 | case 'FunctionDeclaration': 18 | case 'FunctionExpression': { 19 | this.skip(); 20 | break; 21 | } 22 | 23 | case 'ThisExpression': { 24 | result = true; 25 | this.break(); 26 | break; 27 | } 28 | 29 | default: { 30 | // Ignored. 31 | break; 32 | } 33 | } 34 | }, 35 | fallback: 'iteration', 36 | }); 37 | 38 | return result; 39 | } 40 | 41 | module.exports = scopeReferencesThis; 42 | -------------------------------------------------------------------------------- /lib/utils/stack.js: -------------------------------------------------------------------------------- 1 | module.exports = class Stack { 2 | constructor() { 3 | this.stack = new Array(); 4 | } 5 | pop() { 6 | return this.stack.pop(); 7 | } 8 | push(item) { 9 | this.stack.push(item); 10 | } 11 | peek() { 12 | return this.stack.length > 0 ? this.stack.at(-1) : undefined; 13 | } 14 | size() { 15 | return this.stack.length; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /scripts/list-jquery-methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on work from wikimedia/eslint-plugin-no-jquery 3 | * 4 | * https://github.com/wikimedia/eslint-plugin-no-jquery/blob/351eca834e002a056f71072af412bb19255157ce/tools/build-all-methods.js 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const fs = require('fs'); 10 | const { JSDOM } = require('jsdom'); 11 | 12 | const { window } = new JSDOM(''); 13 | const $ = require('jquery')(window); 14 | 15 | const allMethods = Object.keys($) 16 | .filter((k) => !k.startsWith('_') && typeof $[k] === 'function') 17 | .sort() 18 | .map((k) => ` '${k}',`) 19 | .join('\n'); 20 | 21 | fs.writeFile( 22 | 'lib/utils/jquery-methods.js', 23 | `${ 24 | '// This file is built by scripts/list-jquery-methods.js; do not edit it directly.\n' + 25 | 'module.exports = [\n' 26 | }${allMethods}\n];\n`, 27 | (err) => { 28 | if (err) { 29 | throw err; 30 | } 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /scripts/update-rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a modified file that originally was created by: 3 | * @author Toru Nagashima 4 | * @copyright 2017 Toru Nagashima. All rights reserved. 5 | * See LICENSE file in root directory for full license. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | // ------------------------------------------------------------------------------ 11 | // Requirements 12 | // ------------------------------------------------------------------------------ 13 | 14 | const fs = require('fs'); 15 | const path = require('path'); 16 | 17 | // ------------------------------------------------------------------------------ 18 | // Main 19 | // ------------------------------------------------------------------------------ 20 | 21 | function generate(filename, filter) { 22 | const root = path.resolve(__dirname, '../lib/rules'); 23 | const recommendedRulesFile = path.resolve(__dirname, filename); 24 | 25 | const rules = fs 26 | .readdirSync(root) 27 | .filter((file) => path.extname(file) === '.js') 28 | .map((file) => path.basename(file, '.js')) 29 | .map((fileName) => [fileName, require(path.join(root, fileName))]); // eslint-disable-line import/no-dynamic-require 30 | 31 | const recommendedRules = rules.reduce((obj, entry) => { 32 | const name = `ember/${entry[0]}`; 33 | if (filter(entry)) { 34 | obj[name] = 'error'; // eslint-disable-line no-param-reassign 35 | } 36 | return obj; 37 | }, {}); 38 | 39 | const recommendedRulesContent = `/* 40 | * IMPORTANT! 41 | * This file has been automatically generated. 42 | * In order to update its content based on rules' 43 | * definitions, execute "npm run update" 44 | */ 45 | module.exports = ${JSON.stringify(recommendedRules, null, 2)}`; 46 | 47 | fs.writeFileSync(recommendedRulesFile, recommendedRulesContent); 48 | } 49 | 50 | generate('../lib/recommended-rules.js', (entry) => entry[1].meta.docs.recommended); 51 | generate('../lib/recommended-rules-gjs.js', (entry) => entry[1].meta.docs.recommendedGjs); 52 | generate('../lib/recommended-rules-gts.js', (entry) => entry[1].meta.docs.recommendedGts); 53 | -------------------------------------------------------------------------------- /tests/config-setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { readdirSync, readFileSync } = require('fs'); 4 | const path = require('path'); 5 | const configs = require('../lib').configs; 6 | 7 | const CONFIG_NAMES = Object.keys(configs); 8 | 9 | describe('config setup is correct', function () { 10 | it('should have a list of exported configs and config directory that match', function () { 11 | const filePath = path.join(__dirname, '..', 'lib', 'config'); 12 | const files = readdirSync(filePath); 13 | 14 | expect(CONFIG_NAMES).toEqual( 15 | files.filter((file) => !file.startsWith('.')).map((file) => file.replace('.js', '')) 16 | ); 17 | }); 18 | 19 | it('should mention all configs in the README', function () { 20 | const filePath = path.join(__dirname, '..', 'README.md'); 21 | const file = readFileSync(filePath, 'utf8'); 22 | 23 | for (const configName of CONFIG_NAMES) { 24 | expect(file).toContain(configName); 25 | } 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/helpers/babel-eslint-parser.js: -------------------------------------------------------------------------------- 1 | const babelESLint = require('@babel/eslint-parser'); 2 | 3 | function parse(code) { 4 | return babelESLint.parse(code, { 5 | babelOptions: { 6 | configFile: require.resolve('../../.babelrc'), 7 | }, 8 | }); 9 | } 10 | 11 | function parseForESLint(code) { 12 | return babelESLint.parseForESLint(code, { 13 | babelOptions: { 14 | configFile: require.resolve('../../.babelrc'), 15 | }, 16 | }); 17 | } 18 | 19 | module.exports = { 20 | parse, 21 | parseForESLint, 22 | }; 23 | -------------------------------------------------------------------------------- /tests/helpers/faux-context.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parseForESLint } = require('../helpers/babel-eslint-parser'); 4 | const { SourceCode } = require('eslint'); 5 | /** 6 | * Builds a fake ESLint context object that's enough to satisfy the contract 7 | * expected by `getSourceModuleNameForIdentifier` and `isEmberCoreModule` 8 | */ 9 | class FauxContext { 10 | constructor(code, filename = '', report = () => {}) { 11 | const { ast } = parseForESLint(code); 12 | 13 | this.ast = ast; 14 | this.filename = filename; 15 | this.report = report; 16 | this.code = code; 17 | } 18 | 19 | /** 20 | * Does not build the full tree of "ancestors" for the identifier, but 21 | * we only care about the first one; the Program node 22 | */ 23 | getAncestors() { 24 | return [this.ast]; 25 | } 26 | 27 | getFilename() { 28 | return this.filename; 29 | } 30 | 31 | getSourceCode() { 32 | return new SourceCode(this.code, this.ast); 33 | } 34 | } 35 | 36 | module.exports = { 37 | FauxContext, 38 | }; 39 | -------------------------------------------------------------------------------- /tests/helpers/test-case.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | addPrefix, 3 | addComputedImport, 4 | }; 5 | 6 | function addPrefix(testCase, prefix) { 7 | if (typeof testCase === 'string') { 8 | return `${prefix}${testCase}`; 9 | } 10 | 11 | const updated = { 12 | ...testCase, 13 | code: `${prefix}${testCase.code}`, 14 | }; 15 | 16 | if (testCase.output) { 17 | updated.output = `${prefix}${testCase.output}`; 18 | } 19 | 20 | return updated; 21 | } 22 | 23 | function addComputedImport(testCase) { 24 | const importStatement = "import { computed } from '@ember/object';\n"; 25 | return addPrefix(testCase, importStatement); 26 | } 27 | -------------------------------------------------------------------------------- /tests/lib/rules-preprocessor/ember_ts/bar.gts: -------------------------------------------------------------------------------- 1 | export const fortyTwoFromGTS = '42'; 2 | 3 | 6 | -------------------------------------------------------------------------------- /tests/lib/rules-preprocessor/ember_ts/baz.ts: -------------------------------------------------------------------------------- 1 | export const fortyTwoFromTS = '42'; 2 | -------------------------------------------------------------------------------- /tests/lib/rules-preprocessor/ember_ts/foo.gts: -------------------------------------------------------------------------------- 1 | import { fortyTwoFromGTS } from './bar.gts'; 2 | import { fortyTwoFromTS } from './baz.ts'; 3 | 4 | export const fortyTwoLocal = '42'; 5 | 6 | const helloWorldFromTS = fortyTwoFromTS[0] === '4' ? 'hello' : 'world'; 7 | const helloWorldFromGTS = fortyTwoFromGTS[0] === '4' ? 'hello' : 'world'; 8 | const helloWorld = fortyTwoLocal[0] === '4' ? 'hello' : 'world'; 9 | // 10 | 15 | -------------------------------------------------------------------------------- /tests/lib/rules-preprocessor/my-component.gts: -------------------------------------------------------------------------------- 1 | // needed for typed linting 2 | -------------------------------------------------------------------------------- /tests/lib/rules-preprocessor/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "allowJs": true, 5 | "strictNullChecks": true 6 | }, 7 | "include": [ 8 | "**/*.ts", 9 | "**/*.gts" 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /tests/lib/rules/classic-decorator-hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../lib/rules/classic-decorator-hooks'); 4 | const RuleTester = require('eslint').RuleTester; 5 | 6 | const { 7 | ERROR_MESSAGE_INIT_IN_NON_CLASSIC, 8 | ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC, 9 | ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC, 10 | } = rule; 11 | 12 | const ruleTester = new RuleTester({ 13 | parser: require.resolve('@babel/eslint-parser'), 14 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 15 | }); 16 | 17 | ruleTester.run('classic-decorator-hooks', rule, { 18 | valid: [ 19 | ` 20 | const Foo = EmberObject.extend({ 21 | init() {}, 22 | destroy() {}, 23 | ...foo 24 | }) 25 | `, 26 | ` 27 | class Foo extends Bar { 28 | constructor() {} 29 | willDestroy() {} 30 | } 31 | `, 32 | ` 33 | @classic 34 | class Foo extends Bar { 35 | init() {} 36 | destroy() {} 37 | } 38 | `, 39 | ` 40 | class Foo { 41 | init() {} 42 | destroy() {} 43 | } 44 | `, 45 | ], 46 | 47 | invalid: [ 48 | { 49 | code: ` 50 | class Foo extends Bar { 51 | init() {} 52 | } 53 | `, 54 | output: null, 55 | errors: [ 56 | { 57 | message: ERROR_MESSAGE_INIT_IN_NON_CLASSIC, 58 | }, 59 | ], 60 | }, 61 | { 62 | code: ` 63 | class Foo extends Bar { 64 | destroy() {} 65 | } 66 | `, 67 | output: null, 68 | errors: [ 69 | { 70 | message: ERROR_MESSAGE_DESTROY_IN_NON_CLASSIC, 71 | }, 72 | ], 73 | }, 74 | { 75 | code: ` 76 | @classic 77 | class Foo extends Bar { 78 | constructor() {} 79 | } 80 | `, 81 | output: null, 82 | errors: [ 83 | { 84 | message: ERROR_MESSAGE_CONSTRUCTOR_IN_CLASSIC, 85 | }, 86 | ], 87 | }, 88 | ], 89 | }); 90 | -------------------------------------------------------------------------------- /tests/lib/rules/classic-decorator-no-classic-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../lib/rules/classic-decorator-no-classic-methods'); 4 | const RuleTester = require('eslint').RuleTester; 5 | 6 | const { disallowedMethodErrorMessage } = rule; 7 | 8 | const ruleTester = new RuleTester({ 9 | parser: require.resolve('@babel/eslint-parser'), 10 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 11 | }); 12 | 13 | ruleTester.run('classic-decorator-no-classic-methods', rule, { 14 | valid: [ 15 | ` 16 | const Foo = EmberObject.extend({ 17 | foo() { 18 | this.get('bar'); 19 | } 20 | }) 21 | `, 22 | ` 23 | class Foo extends Bar { 24 | foo() { 25 | get(this, 'bar'); 26 | this.baz(); 27 | } 28 | } 29 | `, 30 | ` 31 | @classic 32 | class Foo extends Bar { 33 | foo() { 34 | this.get('bar'); 35 | } 36 | } 37 | `, 38 | ` 39 | class Foo { 40 | foo() { 41 | this.get('bar'); 42 | } 43 | } 44 | `, 45 | ` 46 | class Foo extends Bar { 47 | foo = otherClass.get('bar'); 48 | } 49 | `, 50 | ], 51 | 52 | invalid: [ 53 | { 54 | code: ` 55 | class Foo extends Bar { 56 | foo() { 57 | this.get('bar'); 58 | } 59 | } 60 | `, 61 | output: null, 62 | errors: [ 63 | { 64 | message: disallowedMethodErrorMessage('get'), 65 | type: 'MemberExpression', 66 | }, 67 | ], 68 | }, 69 | { 70 | code: ` 71 | class Foo extends Bar { 72 | foo = this.get('bar'); 73 | } 74 | `, 75 | output: null, 76 | errors: [ 77 | { 78 | message: disallowedMethodErrorMessage('get'), 79 | type: 'MemberExpression', 80 | }, 81 | ], 82 | }, 83 | ], 84 | }); 85 | -------------------------------------------------------------------------------- /tests/lib/rules/closure-actions.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // Requirements 3 | // ------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/closure-actions'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | const { ERROR_MESSAGE } = rule; 9 | 10 | // ------------------------------------------------------------------------------ 11 | // Tests 12 | // ------------------------------------------------------------------------------ 13 | 14 | const eslintTester = new RuleTester({ 15 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 16 | }); 17 | 18 | eslintTester.run('closure-actions', rule, { 19 | valid: [ 20 | 'export default Component.extend();', 21 | 'export default Component.extend({actions: {pushLever() {this.attr.boom();}}});', 22 | ], 23 | invalid: [ 24 | { 25 | code: 'export default Component.extend({actions: {pushLever() {this.sendAction("detonate");}}});', 26 | output: null, 27 | errors: [ 28 | { 29 | message: ERROR_MESSAGE, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }); 35 | -------------------------------------------------------------------------------- /tests/lib/rules/no-at-ember-render-modifiers.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Requirements 3 | //------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/no-at-ember-render-modifiers'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | const { ERROR_MESSAGE } = rule; 9 | 10 | //------------------------------------------------------------------------------ 11 | // Tests 12 | //------------------------------------------------------------------------------ 13 | 14 | const ruleTester = new RuleTester({ 15 | parserOptions: { 16 | ecmaVersion: 2022, 17 | sourceType: 'module', 18 | }, 19 | }); 20 | ruleTester.run('no-at-ember-render-modifiers', rule, { 21 | valid: [ 22 | 'import x from "x"', 23 | '', 24 | "import { x } from 'foo';", 25 | "import x from '@ember/foo';", 26 | "import x from '@ember/render-modifiers-foo';", 27 | ], 28 | invalid: [ 29 | { 30 | code: 'import "@ember/render-modifiers";', 31 | output: null, 32 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 33 | }, 34 | { 35 | code: 'import x from "@ember/render-modifiers";', 36 | output: null, 37 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 38 | }, 39 | { 40 | code: 'import { x } from "@ember/render-modifiers";', 41 | output: null, 42 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 43 | }, 44 | { 45 | code: 'import didInsert from "@ember/render-modifiers/modifiers/did-insert";', 46 | output: null, 47 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 48 | }, 49 | { 50 | code: 'import didUpdate from "@ember/render-modifiers/modifiers/did-update";', 51 | output: null, 52 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 53 | }, 54 | { 55 | code: 'import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";', 56 | output: null, 57 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 58 | }, 59 | ], 60 | }); 61 | -------------------------------------------------------------------------------- /tests/lib/rules/no-capital-letters-in-routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //------------------------------------------------------------------------------ 4 | // Requirements 5 | //------------------------------------------------------------------------------ 6 | 7 | const rule = require('../../../lib/rules/no-capital-letters-in-routes'); 8 | const RuleTester = require('eslint').RuleTester; 9 | 10 | //------------------------------------------------------------------------------ 11 | // Tests 12 | //------------------------------------------------------------------------------ 13 | 14 | const eslintTester = new RuleTester({ 15 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 16 | }); 17 | eslintTester.run('no-capital-letters-in-routes', rule, { 18 | valid: [ 19 | 'this.route("sign-in");', 20 | 'this.route("news", { path: "/:news_id" });', 21 | ` 22 | const routeName="about"; 23 | this.route(routeName); 24 | this.route(DASH_TAB.ACTIVITY);`, 25 | ], 26 | 27 | invalid: [ 28 | { 29 | code: 'this.route("Sign-in");', 30 | output: null, 31 | errors: [ 32 | { 33 | message: "Unexpected capital letter in route's name", 34 | }, 35 | ], 36 | }, 37 | { 38 | code: 'this.route("hOme");', 39 | output: null, 40 | errors: [ 41 | { 42 | message: "Unexpected capital letter in route's name", 43 | }, 44 | ], 45 | }, 46 | { 47 | code: 'this.route("DASH_TAB.ACTIVITY");', 48 | output: null, 49 | errors: [ 50 | { 51 | message: "Unexpected capital letter in route's name", 52 | }, 53 | ], 54 | }, 55 | ], 56 | }); 57 | -------------------------------------------------------------------------------- /tests/lib/rules/no-classic-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //------------------------------------------------------------------------------ 4 | // Requirements 5 | //------------------------------------------------------------------------------ 6 | 7 | const rule = require('../../../lib/rules/no-classic-components'); 8 | const RuleTester = require('eslint').RuleTester; 9 | 10 | //------------------------------------------------------------------------------ 11 | // Tests 12 | //------------------------------------------------------------------------------ 13 | 14 | const { ERROR_MESSAGE } = rule; 15 | const parserOptions = { 16 | ecmaVersion: 2022, 17 | sourceType: 'module', 18 | }; 19 | const ruleTester = new RuleTester({ parserOptions }); 20 | 21 | ruleTester.run('no-classic-components', rule, { 22 | valid: [ 23 | "import Component from '@glimmer/component';", 24 | "import { setComponentTemplate } from '@ember/component';", 25 | "import { helper } from '@ember/component/helper';", 26 | ], 27 | 28 | invalid: [ 29 | { 30 | code: "import Component from '@ember/component';", 31 | output: null, 32 | errors: [ 33 | { 34 | message: ERROR_MESSAGE, 35 | type: 'ImportDefaultSpecifier', 36 | }, 37 | ], 38 | }, 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /tests/lib/rules/no-current-route-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../lib/rules/no-current-route-name'); 4 | const RuleTester = require('eslint').RuleTester; 5 | 6 | const { ERROR_MESSAGE } = rule; 7 | 8 | const ruleTester = new RuleTester({ 9 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 10 | }); 11 | 12 | ruleTester.run('no-current-route-name', rule, { 13 | valid: [ 14 | "assert.equal(currentURL(), '/foo');", 15 | "assert.equal(currentRouteName(), '/foo');", 16 | 17 | ` 18 | import { currentURL } from '@ember/test-helpers'; 19 | 20 | assert.equal(currentURL(), '/foo'); 21 | `, 22 | 23 | // who knows... 24 | ` 25 | import { currentURL as currentRouteName } from '@ember/test-helpers'; 26 | 27 | assert.equal(currentRouteName(), '/foo'); 28 | `, 29 | ], 30 | 31 | invalid: [ 32 | { 33 | code: ` 34 | import { currentRouteName } from '@ember/test-helpers'; 35 | 36 | assert.equal(currentRouteName(), '/foo'); 37 | `, 38 | output: null, 39 | errors: [{ message: ERROR_MESSAGE, line: 4, column: 22 }], 40 | }, 41 | { 42 | code: ` 43 | import { currentRouteName as foo } from '@ember/test-helpers'; 44 | 45 | assert.equal(foo(), '/foo'); 46 | `, 47 | output: null, 48 | errors: [{ message: ERROR_MESSAGE, line: 4, column: 22 }], 49 | }, 50 | ], 51 | }); 52 | -------------------------------------------------------------------------------- /tests/lib/rules/no-html-safe.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Requirements 3 | //------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/no-html-safe'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | const { ERROR_MESSAGE } = rule; 9 | 10 | //------------------------------------------------------------------------------ 11 | // Tests 12 | //------------------------------------------------------------------------------ 13 | 14 | const ruleTester = new RuleTester({ 15 | parserOptions: { 16 | ecmaVersion: 2022, 17 | sourceType: 'module', 18 | }, 19 | }); 20 | 21 | ruleTester.run('no-html-safe', rule, { 22 | valid: [ 23 | // Wrong function: 24 | "import { ANYTHING } from '@ember/string'; ANYTHING('foo');", 25 | "import { ANYTHING } from '@ember/template'; ANYTHING('foo');", 26 | 27 | // Wrong import path: 28 | "import { htmlSafe } from 'something/else'; htmlSafe('foo');", 29 | 30 | // No import: 31 | "htmlSafe('foo');", 32 | "import EmberString from '@ember/string'; htmlSafe('foo');", 33 | 34 | // Wrong object: 35 | "import { htmlSafe } from '@ember/string'; foo.htmlSafe('foo');", 36 | "import { htmlSafe } from '@ember/string'; htmlSafe.foo('foo');", 37 | ], 38 | invalid: [ 39 | { 40 | code: "import { htmlSafe } from '@ember/string'; htmlSafe('foo');", 41 | output: null, 42 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }], 43 | }, 44 | { 45 | code: "import { htmlSafe } from '@ember/template'; htmlSafe('foo');", 46 | output: null, 47 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }], 48 | }, 49 | { 50 | code: "import { htmlSafe as myCustomNameForHtmlSafe } from '@ember/template'; myCustomNameForHtmlSafe();", 51 | output: null, 52 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }], 53 | }, 54 | ], 55 | }); 56 | -------------------------------------------------------------------------------- /tests/lib/rules/no-mixins.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // Requirements 3 | // ------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/no-mixins'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Tests 10 | // ------------------------------------------------------------------------------ 11 | 12 | const { ERROR_MESSAGE } = rule; 13 | const eslintTester = new RuleTester({ 14 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 15 | }); 16 | eslintTester.run('no-mixins', rule, { 17 | valid: [ 18 | ` 19 | import NoMixinRule from "some/addon/mixin"; 20 | export default mixin; 21 | `, 22 | ` 23 | import NameIsMixin from "some/addon"; 24 | export default Component.extend({}); 25 | `, 26 | ` 27 | import * as Mixins from "some/addon/mixins"; 28 | export default Component.extend({}); 29 | `, 30 | ` 31 | import Mixin from '@ember/object/mixin'; 32 | const newMixin = Mixin.create({}); 33 | `, 34 | ], 35 | invalid: [ 36 | { 37 | code: ` 38 | import BadCode from "my-addon/mixins/bad-code"; 39 | 40 | export default Component.extend(BadCode, {}); 41 | `, 42 | output: null, 43 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 44 | }, 45 | { 46 | code: ` 47 | import EmberObject from '@ember/object'; 48 | import EditableMixin from '../mixins/editable'; 49 | 50 | const Comment = EmberObject.extend(EditableMixin, { 51 | post: null 52 | }); 53 | `, 54 | output: null, 55 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 56 | }, 57 | { 58 | code: ` 59 | import Component from '@ember/component'; 60 | import FooMixin from '../utils/mixins/foo'; 61 | 62 | export default class FooComponent extends Component.extend(FooMixin) { 63 | // ... 64 | } 65 | `, 66 | output: null, 67 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 68 | }, 69 | ], 70 | }); 71 | -------------------------------------------------------------------------------- /tests/lib/rules/no-new-mixins.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // Requirements 3 | // ------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/no-new-mixins'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Tests 10 | // ------------------------------------------------------------------------------ 11 | 12 | const { ERROR_MESSAGE } = rule; 13 | const eslintTester = new RuleTester({ 14 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 15 | }); 16 | eslintTester.run('no-new-mixins', rule, { 17 | valid: [ 18 | ` 19 | import mixin from "some/addon"; 20 | export default mixin; 21 | `, 22 | 'export default mixin.create({actions: {},});', 23 | ], 24 | invalid: [ 25 | { 26 | code: ` 27 | import Ember from "ember"; 28 | 29 | export default Ember.Mixin.create({}); 30 | `, 31 | output: null, 32 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }], 33 | }, 34 | { 35 | code: ` 36 | import Mixin from "@ember/object/mixin"; 37 | 38 | export default Mixin.create({}); 39 | `, 40 | output: null, 41 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }], 42 | }, 43 | { 44 | code: ` 45 | import Mixin from "@ember/object/mixin"; 46 | class MyMixin extends Mixin {} 47 | `, 48 | output: null, 49 | errors: [{ message: ERROR_MESSAGE, type: 'ClassDeclaration' }], 50 | }, 51 | ], 52 | }); 53 | -------------------------------------------------------------------------------- /tests/lib/rules/no-proxies.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Requirements 3 | //------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/no-proxies'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | const { ERROR_MESSAGE } = rule; 9 | 10 | //------------------------------------------------------------------------------ 11 | // Tests 12 | //------------------------------------------------------------------------------ 13 | 14 | const ruleTester = new RuleTester({ 15 | parserOptions: { 16 | ecmaVersion: 2022, 17 | sourceType: 'module', 18 | }, 19 | }); 20 | 21 | ruleTester.run('no-proxies', rule, { 22 | valid: [ 23 | "import ANYTHING from '@ember/object';", 24 | "import { ANYTHING } from '@ember/object';", 25 | 26 | "import ObjectProxy from 'something/else';", 27 | "import { ObjectProxy } from 'something/else';", 28 | ], 29 | invalid: [ 30 | { 31 | code: "import ObjectProxy from '@ember/object/proxy';", 32 | output: null, 33 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 34 | }, 35 | { 36 | code: "import ArrayProxy from '@ember/array/proxy';", 37 | output: null, 38 | errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }], 39 | }, 40 | ], 41 | }); 42 | -------------------------------------------------------------------------------- /tests/lib/rules/no-replace-test-comments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //------------------------------------------------------------------------------ 4 | // Requirements 5 | //------------------------------------------------------------------------------ 6 | 7 | const RuleTester = require('eslint').RuleTester; 8 | const rule = require('../../../lib/rules/no-replace-test-comments'); 9 | 10 | const { ERROR_MESSAGE } = rule; 11 | 12 | //------------------------------------------------------------------------------ 13 | // Tests 14 | //------------------------------------------------------------------------------ 15 | 16 | const ruleTester = new RuleTester(); 17 | ruleTester.run('no-replace-test-comments', rule, { 18 | valid: [ 19 | '// some harmless comment', 20 | 'myCodeWithoutAComment = "fooBar"', 21 | { 22 | filename: 'app/some-app-file.js', 23 | code: '// Replace this with your real tests', 24 | }, 25 | ], 26 | 27 | invalid: [ 28 | { 29 | filename: 'test/some-app-file-test.js', 30 | code: '// Replace this with your real tests', 31 | output: null, 32 | errors: [ 33 | { 34 | message: ERROR_MESSAGE, 35 | type: 'Line', 36 | }, 37 | ], 38 | }, 39 | { 40 | filename: 'test/some-app-file-test.js', 41 | code: '// TODO: Replace this with your real tests', 42 | output: null, 43 | errors: [ 44 | { 45 | message: ERROR_MESSAGE, 46 | type: 'Line', 47 | }, 48 | ], 49 | }, 50 | ], 51 | }); 52 | -------------------------------------------------------------------------------- /tests/lib/rules/no-string-prototype-extensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../lib/rules/no-string-prototype-extensions'); 4 | const RuleTester = require('eslint').RuleTester; 5 | 6 | const { ERROR_MESSAGE } = rule; 7 | 8 | const ruleTester = new RuleTester({ 9 | parser: require.resolve('@babel/eslint-parser'), 10 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 11 | }); 12 | 13 | ruleTester.run('no-string-prototype-extensions', rule, { 14 | valid: [ 15 | "import { dasherize } from '@ember/string'; dasherize('myString')", 16 | "import { capitalize } from '@ember/string'; capitalize(someString)", 17 | "import { htmlSafe } from '@ember/template'; htmlSafe('foo')", 18 | "someString['foo']()", 19 | 'something.foo()', 20 | 'dasherize.foo()', 21 | 'this.dasherize()', 22 | 23 | // Still allowed to use directly from Ember: 24 | "import Ember from 'ember'; Ember.String.htmlSafe('foo');", 25 | "import Ember from 'ember'; Ember.String.dasherize('foo');", 26 | ], 27 | 28 | invalid: [ 29 | { 30 | code: "'myString'.dasherize()", 31 | output: null, 32 | errors: [{ message: ERROR_MESSAGE, column: 12, endColumn: 21 }], 33 | }, 34 | { 35 | code: 'someString.capitalize()', 36 | output: null, 37 | errors: [{ message: ERROR_MESSAGE, column: 12, endColumn: 22 }], 38 | }, 39 | { 40 | code: 'getSomeString().dasherize()', 41 | output: null, 42 | errors: [{ message: ERROR_MESSAGE, column: 17, endColumn: 26 }], 43 | }, 44 | { 45 | code: "'foo'.htmlSafe()", 46 | output: null, 47 | errors: [{ message: ERROR_MESSAGE, column: 14, endColumn: 22 }], 48 | }, 49 | ], 50 | }); 51 | -------------------------------------------------------------------------------- /tests/lib/rules/no-test-and-then.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Requirements 3 | //------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/no-test-and-then'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | const { ERROR_MESSAGE } = rule; 9 | 10 | //------------------------------------------------------------------------------ 11 | // Tests 12 | //------------------------------------------------------------------------------ 13 | 14 | const ruleTester = new RuleTester({ 15 | parserOptions: { 16 | ecmaVersion: 2022, 17 | sourceType: 'module', 18 | }, 19 | }); 20 | 21 | const TEST_FILE_NAME = 'some-test.js'; 22 | 23 | ruleTester.run('no-test-and-then', rule, { 24 | valid: [ 25 | { 26 | filename: TEST_FILE_NAME, 27 | code: "run(() => { console.log('Hello World.'); });", 28 | }, 29 | { 30 | filename: TEST_FILE_NAME, 31 | code: 'myCustomClass.andThen(myFunction);', 32 | }, 33 | { 34 | filename: TEST_FILE_NAME, 35 | code: 'andThen.otherFunction(myFunction);', 36 | }, 37 | { 38 | filename: 'not-a-test-file.js', 39 | code: 'andThen(() => { assert.ok(myBool); });', 40 | }, 41 | ], 42 | invalid: [ 43 | { 44 | filename: TEST_FILE_NAME, 45 | code: 'andThen(() => { assert.ok(myBool); });', 46 | output: null, 47 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }], 48 | }, 49 | ], 50 | }); 51 | -------------------------------------------------------------------------------- /tests/lib/rules/no-try-invoke.js: -------------------------------------------------------------------------------- 1 | const rule = require('../../../lib/rules/no-try-invoke'); 2 | const RuleTester = require('eslint').RuleTester; 3 | 4 | const { ERROR_MESSAGE } = rule; 5 | const ruleTester = new RuleTester({ 6 | parserOptions: { 7 | ecmaVersion: 2022, 8 | sourceType: 'module', 9 | }, 10 | }); 11 | 12 | ruleTester.run('no-try-invoke', rule, { 13 | valid: [ 14 | "tryInvoke(this, 'foo');", 15 | "import { tryInvoke } from '@ember/utils'; foo.tryInvoke(this, 'foo');", 16 | "import { tryInvoke } from '@ember/utils'; tryInvoke.foo(this, 'foo');", 17 | "import { tryInvoke } from '@ember/utils'; foo();", 18 | ], 19 | invalid: [ 20 | { 21 | code: ` 22 | import { tryInvoke } from '@ember/utils'; 23 | tryInvoke(this, 'foo'); 24 | `, 25 | output: null, 26 | errors: [ 27 | { 28 | message: ERROR_MESSAGE, 29 | type: 'CallExpression', 30 | }, 31 | ], 32 | }, 33 | { 34 | code: ` 35 | import { tryInvoke, isPresent } from '@ember/utils'; 36 | tryInvoke(this, 'foo', ['bar']); 37 | `, 38 | output: null, 39 | errors: [ 40 | { 41 | message: ERROR_MESSAGE, 42 | type: 'CallExpression', 43 | }, 44 | ], 45 | }, 46 | ], 47 | }); 48 | -------------------------------------------------------------------------------- /tests/lib/rules/require-fetch-import.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../../../lib/rules/require-fetch-import'); 4 | const RuleTester = require('eslint').RuleTester; 5 | 6 | const { ERROR_MESSAGE } = rule; 7 | 8 | const ruleTester = new RuleTester({ 9 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 10 | env: { browser: true }, 11 | }); 12 | 13 | ruleTester.run('require-fetch-import', rule, { 14 | valid: [ 15 | ` 16 | import fetch from 'fetch'; 17 | 18 | fetch('/something'); 19 | `, 20 | ` 21 | fetch('/something'); 22 | 23 | import fetch from 'fetch'; 24 | `, 25 | ` 26 | import { default as fetch } from 'fetch'; 27 | 28 | fetch('/something'); 29 | `, 30 | "foo('/something');", 31 | ], 32 | 33 | invalid: [ 34 | { 35 | code: "fetch('/something');", 36 | output: null, 37 | errors: [{ message: ERROR_MESSAGE, type: 'CallExpression', line: 1, column: 1 }], 38 | }, 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /tests/lib/rules/template-no-let-reference.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // Requirements 3 | //------------------------------------------------------------------------------ 4 | 5 | const rule = require('../../../lib/rules/template-no-let-reference'); 6 | const RuleTester = require('eslint').RuleTester; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Tests 10 | //------------------------------------------------------------------------------ 11 | 12 | const ruleTester = new RuleTester({ 13 | parser: require.resolve('ember-eslint-parser'), 14 | parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, 15 | }); 16 | ruleTester.run('template-no-let-reference', rule, { 17 | valid: [ 18 | ` 19 | const a = ''; 20 | function create(d) { 21 | 30 | } 31 | `, 32 | ` 33 | const a = ''; 34 | 37 | `, 38 | // make sure rule does not error out on missing references 39 | ` 40 | const a = ''; 41 | 45 | `, 46 | ], 47 | 48 | invalid: [ 49 | { 50 | code: ` 51 | let a = ''; 52 | 55 | `, 56 | output: null, 57 | errors: [{ type: 'VarHead', message: rule.meta.messages['no-let'] }], 58 | }, 59 | { 60 | code: ` 61 | var a = ''; 62 | 65 | `, 66 | output: null, 67 | errors: [{ type: 'GlimmerElementNodePart', message: rule.meta.messages['no-let'] }], 68 | }, 69 | ], 70 | }); 71 | -------------------------------------------------------------------------------- /tests/lib/utils/computed-properties-test.js: -------------------------------------------------------------------------------- 1 | const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser'); 2 | const computedPropertyUtils = require('../../../lib/utils/computed-properties'); 3 | 4 | function parse(code) { 5 | return babelESLintParse(code).body[0].expression; 6 | } 7 | 8 | describe('getComputedPropertyFunctionBody', () => { 9 | it('gets the result with no dependent keys and no function', () => { 10 | const node = parse('computed()'); 11 | expect(computedPropertyUtils.getComputedPropertyFunctionBody(node)).toBeUndefined(); 12 | }); 13 | 14 | it('gets the result with one dependent key and no function', () => { 15 | const node = parse('computed("dep")'); 16 | expect(computedPropertyUtils.getComputedPropertyFunctionBody(node)).toBeUndefined(); 17 | }); 18 | 19 | it('gets the result when using FunctionExpression and no dependent keys', () => { 20 | const node = parse('computed(function() {})'); 21 | expect(computedPropertyUtils.getComputedPropertyFunctionBody(node).type).toBe('BlockStatement'); 22 | }); 23 | 24 | it('gets the result when using FunctionExpression and one dependent key', () => { 25 | const node = parse('computed("dep1", function() {})'); 26 | expect(computedPropertyUtils.getComputedPropertyFunctionBody(node).type).toBe('BlockStatement'); 27 | }); 28 | 29 | it('gets the result when using explicit getter', () => { 30 | const node = parse('computed("dep1", { get() {}})'); 31 | expect(computedPropertyUtils.getComputedPropertyFunctionBody(node).type).toBe('BlockStatement'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/lib/utils/javascript-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const javascriptUtils = require('../../../lib/utils/javascript'); 4 | 5 | describe('duplicateArrays', () => { 6 | it('returns the right result', () => { 7 | expect(javascriptUtils.duplicateArrays([], 2)).toStrictEqual([]); 8 | expect(javascriptUtils.duplicateArrays(['abc'], 2)).toStrictEqual(['abc', 'abc', 'abc']); 9 | expect(javascriptUtils.duplicateArrays(['first', 'second'], 2)).toStrictEqual([ 10 | 'first', 11 | 'second', 12 | 'first', 13 | 'second', 14 | 'first', 15 | 'second', 16 | ]); 17 | }); 18 | }); 19 | 20 | describe('removeWhitespace', () => { 21 | it('returns the right result', () => { 22 | expect(javascriptUtils.removeWhitespace('abcdef')).toBe('abcdef'); 23 | expect(javascriptUtils.removeWhitespace('abc def')).toBe('abcdef'); 24 | expect(javascriptUtils.removeWhitespace(' abc def ')).toBe('abcdef'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/lib/utils/new-module-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser'); 4 | const { buildFix, isDestructuring } = require('../../../lib/utils/new-module'); 5 | 6 | const modulesData = { 7 | 'ember-data/model': { 8 | default: ['@ember-data/model'], 9 | }, 10 | }; 11 | 12 | describe('isDestructuring', () => { 13 | it('should check a destructuring import', () => { 14 | const node = babelESLintParse('const { destructured } = someVar;').body[0].declarations[0]; 15 | expect(isDestructuring(node)).toBeTruthy(); 16 | }); 17 | 18 | it('should check a non-destructuring import', () => { 19 | const node = babelESLintParse('const destructured = someVar;').body[0].declarations[0]; 20 | expect(isDestructuring(node)).toBeFalsy(); 21 | }); 22 | 23 | it('should check a ForInStatement', () => { 24 | const node = babelESLintParse('for (const item in list) {};').body[0].left.declarations[0]; 25 | expect(isDestructuring(node)).toBeFalsy(); 26 | }); 27 | }); 28 | 29 | describe('buildFix', () => { 30 | it('returns a function', () => { 31 | const node = babelESLintParse('import Model from "ember-data/model"').body[0]; 32 | const fix = buildFix(node, modulesData); 33 | expect(typeof fix === 'function').toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/lib/utils/property-setter-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser'); 4 | const propertySetterUtils = require('../../../lib/utils/property-setter'); 5 | 6 | function parse(code) { 7 | return babelESLintParse(code).body[0]; 8 | } 9 | 10 | describe('isThisSet', () => { 11 | it('behaves correctly', () => { 12 | // False: 13 | expect(propertySetterUtils.isThisSet(parse('this.x').expression)).toBeFalsy(); 14 | expect(propertySetterUtils.isThisSet(parse('this.x()').expression)).toBeFalsy(); 15 | expect(propertySetterUtils.isThisSet(parse('let x = 123;'))).toBeFalsy(); 16 | expect(propertySetterUtils.isThisSet(parse('x = 123;'))).toBeFalsy(); 17 | 18 | // True: 19 | expect(propertySetterUtils.isThisSet(parse('this.x = 123').expression)).toBeTruthy(); 20 | expect(propertySetterUtils.isThisSet(parse('this.x.y = 123').expression)).toBeTruthy(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/lib/utils/scope-references-this-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse: babelESLintParse } = require('../../helpers/babel-eslint-parser'); 4 | const scopeReferencesThis = require('../../../lib/utils/scope-references-this'); 5 | 6 | function parse(code) { 7 | return babelESLintParse(code).body[0]; 8 | } 9 | 10 | describe('scopeReferencesThis', function () { 11 | it('recognizes simple cases`', function () { 12 | expect(scopeReferencesThis(parse('this'))).toBeTruthy(); // `this` uses `this` 13 | expect(scopeReferencesThis(parse('"this"'))).toBeFalsy(); // the string "this" does not use this 14 | expect(scopeReferencesThis(parse('class Foo { @someDecorator() someProp }'))).toBeFalsy(); // Does not throw with node type (ClassProperty) not handled by estraverse. 15 | }); 16 | 17 | it('can find nested `this`', function () { 18 | expect(scopeReferencesThis(parse('if (a) { this } else { null }'))).toBeTruthy(); // if statement uses this 19 | expect(scopeReferencesThis(parse('() => this'))).toBeTruthy(); // arrow function uses outer this 20 | }); 21 | 22 | it('does not consider `this` within non-arrow functions', function () { 23 | expect(scopeReferencesThis(parse('function foo() { return this; }'))).toBeFalsy(); // function uses different this 24 | expect(scopeReferencesThis(parse('function foo() { return () => this; }'))).toBeFalsy(); // inner arrow function uses different this' 25 | expect(scopeReferencesThis(parse('() => function() { return this; }'))).toBeFalsy(); // inner function uses different this 26 | expect(scopeReferencesThis(parse('({ a() { this } })'))).toBeFalsy(); // object method uses different this 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/plugin-exports.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const plugin = require('../lib'); 4 | const ember = require('../lib/utils/ember'); 5 | const base = require('../lib/config-legacy/base'); 6 | const recommended = require('../lib/config-legacy/recommended'); 7 | const recommendedGjs = require('../lib/config-legacy/recommended-gjs'); 8 | const recommendedGts = require('../lib/config-legacy/recommended-gts'); 9 | 10 | describe('plugin exports', () => { 11 | describe('utils', () => { 12 | it('has the right util functions', () => { 13 | expect(plugin.utils).toStrictEqual({ ember }); 14 | }); 15 | }); 16 | 17 | describe('configs', () => { 18 | it('has the right configurations', () => { 19 | expect(plugin.configs).toStrictEqual({ 20 | base, 21 | recommended, 22 | 'recommended-gjs': recommendedGjs, 23 | 'recommended-gts': recommendedGts, 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/recommended.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const rules = require('../lib').rules; 3 | 4 | describe('recommended rules', () => { 5 | it('has the right list', () => { 6 | const errors = []; 7 | 8 | for (const name of Object.keys(rules)) { 9 | if (rules[name].meta.docs.recommended) { 10 | errors.push(name); 11 | } 12 | } 13 | 14 | expect(errors).toMatchSnapshot(); 15 | }); 16 | 17 | it('gjs config has the right list', () => { 18 | const errors = []; 19 | 20 | for (const name of Object.keys(rules)) { 21 | if (rules[name].meta.docs.recommendedGjs) { 22 | errors.push(name); 23 | } 24 | } 25 | 26 | expect(errors).toMatchSnapshot(); 27 | }); 28 | 29 | it('gts config has the right list', () => { 30 | const errors = []; 31 | 32 | for (const name of Object.keys(rules)) { 33 | if (rules[name].meta.docs.recommendedGts) { 34 | errors.push(name); 35 | } 36 | } 37 | 38 | expect(errors).toMatchSnapshot(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /vitest.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | setupFiles: [], 7 | include: ['**/tests/**/*.js'], 8 | exclude: ['tests/helpers/**', 'node_modules'], 9 | sequence: { 10 | hooks: 'list', 11 | }, 12 | coverage: { 13 | branches: 95, 14 | functions: 98.95, 15 | lines: 98, 16 | statements: 98, 17 | }, 18 | }, 19 | }); 20 | --------------------------------------------------------------------------------