diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ded66ac31b3..1e4861de3cdf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,8 @@ jobs: install: name: Checkout and Install runs-on: ubuntu-latest + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -51,6 +53,8 @@ jobs: name: Build All Packages needs: [install] runs-on: ubuntu-latest + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -65,6 +69,8 @@ jobs: name: Generate Configs needs: [build] runs-on: ubuntu-latest + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -84,6 +90,8 @@ jobs: strategy: matrix: lint-task: ['check-spelling', 'check-format', 'lint-markdown'] + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -103,6 +111,8 @@ jobs: strategy: matrix: lint-task: ['lint', 'typecheck', 'knip'] + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -122,6 +132,8 @@ jobs: name: Stylelint needs: [install] runs-on: ubuntu-latest + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -137,6 +149,8 @@ jobs: name: Run integration tests on primary Node.js version needs: [build] runs-on: ubuntu-latest + env: + NX_CI_EXECUTION_ENV: 'ubuntu-latest' steps: - name: Checkout uses: actions/checkout@v4 @@ -179,8 +193,7 @@ jobs: 'visitor-keys', ] env: - # Added the - at the end to function as a separator to improve readability in the PR comment from the Nx cloud app - NX_CLOUD_ENV_NAME: 'Node ${{ matrix.node-version }} -' + NX_CI_EXECUTION_ENV: '${{ matrix.os }} - Node ${{ matrix.node-version }}' COLLECT_COVERAGE: false steps: - name: Checkout @@ -225,7 +238,8 @@ jobs: package: ['eslint-plugin', 'eslint-plugin-internal', 'typescript-estree'] env: - COLLECT_COVERAGE: false + NX_CI_EXECUTION_ENV: 'ubuntu-latest' + COLLECT_COVERAGE: false, steps: - name: Checkout uses: actions/checkout@v4 diff --git a/knip.ts b/knip.ts index 9f2a79146687..327290d9019a 100644 --- a/knip.ts +++ b/knip.ts @@ -2,6 +2,7 @@ import type { KnipConfig } from 'knip' with { 'resolution-mode': 'import' }; export default { rules: { + binaries: 'off', classMembers: 'off', duplicates: 'off', enumMembers: 'off', @@ -54,11 +55,13 @@ export default { }, }, 'packages/eslint-plugin': { + entry: ['tools/**'], ignore: [ 'tests/fixtures/**', 'typings/eslint-rules.d.ts', 'typings/typescript.d.ts', ], + ignoreDependencies: ['tsx'], // used in nx target definitions }, 'packages/eslint-plugin-internal': { ignore: ['tests/fixtures/**'], diff --git a/nx.json b/nx.json index 167f99ebcc2a..bea912313ce5 100644 --- a/nx.json +++ b/nx.json @@ -4,7 +4,7 @@ "plugins": [ { "plugin": "@nx/js/typescript", - "exclude": [".", "packages/integration-tests/fixtures/**"], + "exclude": ["packages/integration-tests/fixtures/**"], "options": { "typecheck": { "targetName": "typecheck" @@ -17,17 +17,9 @@ }, { "plugin": "@nx/vite/plugin", - "include": ["packages/*"], + "exclude": ["*"], "options": { - "buildTargetName": "vite:build", - "testTargetName": "test", - "serveTargetName": "serve", - "devTargetName": "dev", - "previewTargetName": "preview", - "serveStaticTargetName": "serve-static", - "typecheckTargetName": "vite:typecheck", - "buildDepsTargetName": "vite:build-deps", - "watchDepsTargetName": "vite:watch-deps" + "testTargetName": "test" } }, { @@ -59,64 +51,8 @@ } }, "targetDefaults": { - "build": { - "dependsOn": ["^build"], - "inputs": ["production", "^production"], - "outputs": ["{projectRoot}/dist"], - "options": { - "cwd": "{projectRoot}" - }, - "cache": true - }, "test": { - "dependsOn": ["^build"], - "outputs": ["{projectRoot}/coverage"], - "cache": true - }, - "@nx/vite:test": { - "dependsOn": ["^build"], - "inputs": [ - "default", - "^production", - "{workspaceRoot}/vitest.config.mts", - "{workspaceRoot}/vitest.config.base.mts", - "{projectRoot}/vitest.config.mts" - ], - "outputs": ["{projectRoot}/coverage"], - "cache": true, - "options": { - "config": "{projectRoot}/vitest.config.mts", - "watch": false - } - }, - "@nx/eslint:lint": { - "dependsOn": ["eslint-plugin-internal:build", "typescript-eslint:build"], - "options": { - "noEslintrc": true, - "cache": true, - "eslintConfig": "{workspaceRoot}/eslint.config.mjs" - }, - "outputs": ["{options.outputFile}"], - "cache": true - }, - "lint": { - "executor": "@nx/eslint:lint", - "dependsOn": ["eslint-plugin-internal:build", "typescript-eslint:build"], - "inputs": [ - "default", - "{workspaceRoot}/eslint.config.mjs", - { - "dependentTasksOutputFiles": "**/*.js", - "transitive": false - } - ], - "outputs": ["{options.outputFile}"], - "cache": true - }, - "typecheck": { - "dependsOn": ["types:copy-ast-spec"], - "outputs": ["{workspaceRoot}/dist"], - "cache": true + "outputs": ["{projectRoot}/coverage"] } }, "namedInputs": { @@ -131,16 +67,15 @@ }, { "runtime": "yarn -v" - }, - "{workspaceRoot}/yarn.lock" + } ], "production": [ "default", "!{projectRoot}/**/?(*.)+(test).?(m|c)[jt]s?(x)?(.snap|.shot)", "!{projectRoot}/tests", + "!{projectRoot}/tools", "!{projectRoot}/tsconfig.spec.json", - "!{projectRoot}/vitest.config.mts", - "!{projectRoot}/src/test-setup.[jt]s" + "!{projectRoot}/vitest.config.mts" ] } } diff --git a/package.json b/package.json index a3d9c5c307bd..8dde6b1ec8fb 100644 --- a/package.json +++ b/package.json @@ -22,30 +22,30 @@ }, "homepage": "https://typescript-eslint.io", "scripts": { - "build": "nx run-many --target=build --exclude website --exclude website-eslint", + "build": "nx run-many -t build --exclude website website-eslint", "check-clean-workspace-after-install": "git diff --quiet --exit-code", "check-format": "prettier --check .", "check-spelling": "cspell --config=.cspell.json \"**/*.{md,mdx,ts,mts,cts,js,cjs,mjs,tsx,jsx}\" --no-progress --show-context --show-suggestions", - "clean": "nx run-many --target=clean --parallel=20", + "clean": "nx run-many -t clean --parallel=20", "format": "prettier --ignore-path=$PROJECT_CWD/.prettierignore --config=$PROJECT_CWD/.prettierrc.json --write $INIT_CWD", "generate-breaking-changes": "nx run eslint-plugin:generate-breaking-changes", - "generate-configs": "nx generate-configs", - "generate-contributors": "nx generate-contributors", - "generate-lib": "nx generate-lib", - "generate-sponsors": "nx generate-sponsors", + "generate-configs": "tsx tools/scripts/generate-configs.mts", + "generate-contributors": "tsx tools/scripts/generate-contributors.mts", + "generate-lib": "tsx tools/scripts/generate-lib.mts", + "generate-sponsors": "tsx tools/scripts/generate-sponsors.mts", "generate-website-dts": "nx run website:generate-website-dts", "lint-fix": "yarn lint --fix", "lint-markdown-fix": "yarn lint-markdown --fix", "lint-markdown": "markdownlint \"**/*.md\" --config=.markdownlint.json --ignore-path=.markdownlintignore", "lint-stylelint": "nx lint website stylelint", - "lint": "nx run-many --target=lint", + "lint": "nx run-many -t lint", "postinstall": "tsx tools/scripts/postinstall.mts", "pre-commit": "lint-staged", "release": "tsx tools/release/release.mts", "start": "nx run website:start", - "test": "nx run-many --target=test --exclude integration-tests --exclude website --exclude website-eslint", + "test": "nx run-many -t test --exclude integration-tests website website-eslint", "test-integration": "nx run integration-tests:test", - "typecheck": "nx run-many --target=typecheck" + "typecheck": "nx run-many -t typecheck" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -57,11 +57,11 @@ "@eslint/compat": "^1.2.4", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.26.0", - "@nx/devkit": "20.7.2", - "@nx/eslint": "20.7.2", - "@nx/js": "20.7.2", - "@nx/vite": "20.7.2", - "@nx/workspace": "20.7.2", + "@nx/devkit": "21.0.3", + "@nx/eslint": "21.0.3", + "@nx/js": "21.0.3", + "@nx/vite": "21.0.3", + "@nx/workspace": "21.0.3", "@swc/core": "^1.4.12", "@types/debug": "^4.1.12", "@types/eslint-plugin-jsx-a11y": "^6.10.0", @@ -97,7 +97,7 @@ "knip": "^5.41.1", "lint-staged": "^15.2.2", "markdownlint-cli": "^0.44.0", - "nx": "20.7.2", + "nx": "21.0.3", "prettier": "3.5.0", "rimraf": "^5.0.5", "semver": "7.7.0", @@ -122,6 +122,47 @@ }, "packageManager": "yarn@3.8.2", "nx": { - "includedScripts": [] + "name": "repo", + "includedScripts": [ + "generate-configs", + "generate-contributors", + "generate-lib", + "generate-sponsors" + ], + "targets": { + "generate-configs": { + "dependsOn": [ + "eslint-plugin:build" + ] + }, + "generate-lib": { + "dependsOn": [ + "typescript-eslint:build", + "eslint-plugin-internal:build" + ] + }, + "// These targets are used for repo level utils and checking repo files which do not belong to specific published packages": {}, + "typecheck": { + "command": "tsc -b tsconfig.repo-config-files.json", + "dependsOn": [ + "types:copy-ast-spec" + ], + "outputs": [ + "{workspaceRoot}/dist" + ], + "cache": true + }, + "lint": { + "command": "eslint . --ignore-pattern=packages --cache", + "dependsOn": [ + "typescript-eslint:build", + "eslint-plugin-internal:build" + ], + "cache": false + }, + "clean": { + "command": "rimraf dist/ coverage/ .eslintcache" + } + } } } diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index f2532092d1e2..b88610c936ca 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -31,13 +31,14 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "tsc -b tsconfig.build.json && api-extractor run --local --config=$INIT_CWD/api-extractor.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", "clean-fixtures": "rimraf -g \"./src/**/fixtures/**/snapshots\"", "format": "yarn run -T format", "lint": "yarn run -BT nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "funding": { "type": "opencollective", @@ -59,5 +60,37 @@ "rimraf": "*", "typescript": "*", "vitest": "^3.1.3" + }, + "nx": { + "name": "ast-spec", + "implicitDependencies": [ + "!typescript-estree" + ], + "includedScripts": [ + "clean", + "clean-fixtures" + ], + "targets": { + "build": { + "command": "tsc -b tsconfig.build.json && api-extractor run --local --config=api-extractor.json", + "options": { + "cwd": "{projectRoot}" + }, + "outputs": [ + "{projectRoot}/dist/**/*.ts" + ], + "cache": true + }, + "typecheck": { + "dependsOn": [ + "typescript-estree:build" + ] + }, + "test": { + "dependsOn": [ + "typecheck" + ] + } + } } } diff --git a/packages/ast-spec/project.json b/packages/ast-spec/project.json deleted file mode 100644 index d6c8c081f226..000000000000 --- a/packages/ast-spec/project.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "ast-spec", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "implicitDependencies": ["!typescript-estree"], - "root": "packages/ast-spec", - "sourceRoot": "packages/ast-spec/src", - "targets": { - "build": { - "outputs": ["{projectRoot}/dist/**/*.ts"] - }, - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/vite:test", - "dependsOn": ["typecheck"] - }, - "typecheck": { - "dependsOn": ["typescript-estree:build"] - } - } -} diff --git a/packages/ast-spec/vitest.config.mts b/packages/ast-spec/vitest.config.mts index 090170ba88af..d79590018e5b 100644 --- a/packages/ast-spec/vitest.config.mts +++ b/packages/ast-spec/vitest.config.mts @@ -19,11 +19,6 @@ const vitestConfig = mergeConfig( './tests/util/setupVitest.mts', './tests/util/custom-matchers/custom-matchers.ts', ], - - typecheck: { - enabled: true, - tsconfig: path.join(import.meta.dirname, 'tsconfig.spec.json'), - }, }, }), ); diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index c2cde3c76ee8..303842fa29ba 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -14,12 +14,13 @@ "homepage": "https://typescript-eslint.io", "license": "MIT", "scripts": { - "build": "tsc -b tsconfig.build.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", "format": "yarn run -T format", "lint": "yarn run -BT nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { "@prettier/sync": "^0.5.1", @@ -33,5 +34,11 @@ "@vitest/coverage-v8": "^3.1.3", "rimraf": "*", "vitest": "^3.1.3" + }, + "nx": { + "name": "eslint-plugin-internal", + "includedScripts": [ + "clean" + ] } } diff --git a/packages/eslint-plugin-internal/project.json b/packages/eslint-plugin-internal/project.json deleted file mode 100644 index f0bfaacd26b6..000000000000 --- a/packages/eslint-plugin-internal/project.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "eslint-plugin-internal", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/eslint-plugin-internal", - "sourceRoot": "packages/eslint-plugin-internal/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/vite:test" - } - } -} diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index f198f162124c..a48904ddb6e2 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -49,15 +49,15 @@ "typescript" ], "scripts": { - "build": "tsc -b tsconfig.build.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", "format": "yarn run -T format", - "generate-breaking-changes": "tsx tools/generate-breaking-changes.mts", + "generate-breaking-changes": "yarn run -BT nx generate-breaking-changes", "generate-configs": "yarn run -T generate-configs", "lint": "yarn run -BT nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "test-single": "vitest --run --config=$INIT_CWD/vitest.config.mts --no-coverage", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", @@ -101,5 +101,22 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "nx": { + "name": "eslint-plugin", + "includedScripts": [ + "clean" + ], + "targets": { + "generate-breaking-changes": { + "command": "tsx tools/generate-breaking-changes.mts", + "options": { + "cwd": "{projectRoot}" + }, + "dependsOn": [ + "type-utils:build" + ] + } + } } } diff --git a/packages/eslint-plugin/project.json b/packages/eslint-plugin/project.json deleted file mode 100644 index acfd65a8f79c..000000000000 --- a/packages/eslint-plugin/project.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "eslint-plugin", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/eslint-plugin", - "sourceRoot": "packages/eslint-plugin/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test-single": { - "executor": "@nx/vite:test", - "cache": false, - "options": { - "coverage": false - } - }, - "test": { - "executor": "@nx/vite:test" - }, - "generate-breaking-changes": { - "dependsOn": ["type-utils:build"], - "executor": "nx:run-script", - "options": { - "script": "generate-breaking-changes" - } - } - } -} diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 250cd92b8b07..df5b3ff213b8 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -12,13 +12,18 @@ "homepage": "https://typescript-eslint.io", "license": "MIT", "scripts": { + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", "format": "yarn run -T format", "lint": "yarn run -BT nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "devDependencies": { "@vitest/coverage-v8": "^3.1.3", "vitest": "^3.1.3" + }, + "nx": { + "name": "integration-tests", + "includedScripts": [] } } diff --git a/packages/integration-tests/project.json b/packages/integration-tests/project.json deleted file mode 100644 index 7686e219f332..000000000000 --- a/packages/integration-tests/project.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "integration-tests", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/integration-tests", - "sourceRoot": "packages/integration-tests/tools", - "implicitDependencies": ["typescript-eslint"], - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/vite:test" - } - } -} diff --git a/packages/parser/package.json b/packages/parser/package.json index c0e124b4f28d..99d63e8e0084 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -39,12 +39,13 @@ "eslint" ], "scripts": { - "build": "tsc -b tsconfig.build.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", "format": "yarn run -T format", "lint": "yarn run -BT nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", @@ -67,5 +68,11 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "nx": { + "name": "parser", + "includedScripts": [ + "clean" + ] } } diff --git a/packages/parser/project.json b/packages/parser/project.json deleted file mode 100644 index fe0a92487dde..000000000000 --- a/packages/parser/project.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "parser", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/parser", - "sourceRoot": "packages/parser/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/vite:test" - } - } -} diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index f2e18a13c669..8e4963cb2b52 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -24,12 +24,13 @@ "homepage": "https://typescript-eslint.io", "license": "MIT", "scripts": { - "build": "tsc -b tsconfig.build.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", "format": "yarn run -T format", "lint": "yarn run -BT nx lint", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "dependencies": { "@typescript-eslint/type-utils": "8.32.1", @@ -39,11 +40,18 @@ }, "devDependencies": { "@vitest/coverage-v8": "^3.1.3", + "rimraf": "*", "typescript": "*", "vitest": "^3.1.3" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "nx": { + "name": "rule-schema-to-typescript-types", + "includedScripts": [ + "clean" + ] } } diff --git a/packages/rule-schema-to-typescript-types/project.json b/packages/rule-schema-to-typescript-types/project.json deleted file mode 100644 index 954e08a4001c..000000000000 --- a/packages/rule-schema-to-typescript-types/project.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "rule-schema-to-typescript-types", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/rule-schema-to-typescript-types", - "sourceRoot": "packages/rule-schema-to-typescript-types/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - } - } -} diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index 1689bdd5372e..dfee240b8094 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -35,14 +35,13 @@ "estree" ], "scripts": { - "build": "tsc -b tsconfig.build.json", + "//": "These package scripts are mostly here for convenience. Task running is handled by Nx at the root level.", + "build": "yarn run -BT nx build", "clean": "rimraf dist/ coverage/", "format": "yarn run -T format", "lint": "yarn run -BT nx lint", - "pretest-eslint-base": "tsc -b tsconfig.build.json", - "test-eslint-base": "mocha --require source-map-support/register ./tests/eslint-base/eslint-base.test.js", - "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", - "check-types": "yarn run -BT nx typecheck" + "test": "yarn run -BT nx test", + "typecheck": "yarn run -BT nx typecheck" }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { @@ -61,15 +60,8 @@ "@types/json-stable-stringify-without-jsonify": "^1.0.2", "@types/lodash.merge": "4.6.9", "@vitest/coverage-v8": "^3.1.3", - "chai": "^5.2.0", "eslint": "*", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esprima": "^4.0.1", - "mocha": "^11.0.0", "rimraf": "*", - "sinon": "^20.0.0", - "source-map-support": "^0.5.21", "typescript": "*", "vitest": "^3.1.3" }, @@ -79,5 +71,11 @@ }, "publishConfig": { "access": "public" + }, + "nx": { + "name": "rule-tester", + "includedScripts": [ + "clean" + ] } } diff --git a/packages/rule-tester/project.json b/packages/rule-tester/project.json deleted file mode 100644 index 0dfef3253a40..000000000000 --- a/packages/rule-tester/project.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "rule-tester", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "root": "packages/rule-tester", - "sourceRoot": "packages/rule-tester/src", - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/vite:test" - } - } -} diff --git a/packages/rule-tester/tests/eslint-base/eslint-base.test.js b/packages/rule-tester/tests/eslint-base/eslint-base.test.js deleted file mode 100644 index e87ece634534..000000000000 --- a/packages/rule-tester/tests/eslint-base/eslint-base.test.js +++ /dev/null @@ -1,3536 +0,0 @@ -/** - * This file intentionally does not match the standards in the rest of our codebase. - * It's intended to exactly match the test in ESLint core so we can ensure we - * have compatibility. - * It's tempting to switch this to be strictly typed in TS and to use jest - but - * it's too easy to introduce subtle changes into the test by doing that. It also - * makes it much harder to merge upstream changes into this test. - * - * The only edits we have made are to update the paths for our rep - * - * Forked from https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/tests/lib/rule-tester/rule-tester.js - * - * @noformat - */ -/* eslint-disable */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ -const sinon = require("sinon"), - EventEmitter = require("events"), - { RuleTester } = require("../../dist/RuleTester"), - assert = require("chai").assert, - nodeAssert = require("assert"), - espree = require("espree"); - -const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => { - try { - nodeAssert.strictEqual(1, 2); - } catch (err) { - return err.operator; - } - throw new Error("unexpected successful assertion"); -})(); - -/** - * Do nothing. - * @returns {void} - */ -function noop() { - - // do nothing. -} - -//------------------------------------------------------------------------------ -// Rewire Things -//------------------------------------------------------------------------------ - -/* - * So here's the situation. Because RuleTester uses it() and describe() from - * Mocha, any failures would show up in the output of this test file. That means - * when we tested that a failure is thrown, that would also count as a failure - * in the testing for RuleTester. In order to remove those results from the - * results of this file, we need to overwrite it() and describe() just in - * RuleTester to do nothing but run code. Effectively, it() and describe() - * just become regular functions inside of index.js, not at all related to Mocha. - * That allows the results of this file to be untainted and therefore accurate. - * - * To assert that the right arguments are passed to RuleTester.describe/it, an - * event emitter is used which emits the arguments. - */ - -const ruleTesterTestEmitter = new EventEmitter(); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("RuleTester", () => { - - // Stub `describe()` and `it()` while this test suite. - before(() => { - RuleTester.describe = function(text, method) { - ruleTesterTestEmitter.emit("describe", text, method); - return method.call(this); - }; - RuleTester.it = function(text, method) { - ruleTesterTestEmitter.emit("it", text, method); - return method.call(this); - }; - }); - after(() => { - RuleTester.describe = null; - RuleTester.it = null; - }); - - let ruleTester; - - /** - * A helper function to verify Node.js core error messages. - * @param {string} actual The actual input - * @param {string} expected The expected input - * @returns {Function} Error callback to verify that the message is correct - * for the actual and expected input. - */ - function assertErrorMatches(actual, expected) { - const err = new nodeAssert.AssertionError({ - actual, - expected, - operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR - }); - - return err.message; - } - - beforeEach(() => { - RuleTester.resetDefaultConfig(); - ruleTester = new RuleTester(); - }); - - describe("only", () => { - describe("`itOnly` accessor", () => { - describe("when `itOnly` is set", () => { - before(() => { - RuleTester.itOnly = sinon.spy(); - }); - after(() => { - RuleTester.itOnly = void 0; - }); - beforeEach(() => { - RuleTester.itOnly.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("is called by exclusive tests", () => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(RuleTester.itOnly, "const notVar = 42;"); - }); - }); - - describe("when `it` is set and has an `only()` method", () => { - before(() => { - RuleTester.it.only = () => {}; - sinon.spy(RuleTester.it, "only"); - }); - after(() => { - RuleTester.it.only = void 0; - }); - beforeEach(() => { - RuleTester.it.only.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("is called by tests with `only` set", () => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(RuleTester.it.only, "const notVar = 42;"); - }); - }); - - describe("when global `it` is a function that has an `only()` method", () => { - let originalGlobalItOnly; - - before(() => { - - /* - * We run tests with `--forbid-only`, so we have to override - * `it.only` to prevent the real one from being called. - */ - originalGlobalItOnly = it.only; - it.only = () => {}; - sinon.spy(it, "only"); - }); - after(() => { - it.only = originalGlobalItOnly; - }); - beforeEach(() => { - it.only.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("is called by tests with `only` set", () => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(it.only, "const notVar = 42;"); - }); - }); - - describe("when `describe` and `it` are overridden without `itOnly`", () => { - let originalGlobalItOnly; - - before(() => { - - /* - * These tests override `describe` and `it` already, so we - * don't need to override them here. We do, however, need to - * remove `only` from the global `it` to prevent it from - * being used instead. - */ - originalGlobalItOnly = it.only; - it.only = void 0; - }); - after(() => { - it.only = originalGlobalItOnly; - }); - beforeEach(() => { - ruleTester = new RuleTester(); - }); - - it("throws an error recommending overriding `itOnly`", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - }, "Set `RuleTester.itOnly` to use `only` with a custom test framework."); - }); - }); - - describe("when global `it` is a function that does not have an `only()` method", () => { - let originalGlobalIt; - let originalRuleTesterDescribe; - let originalRuleTesterIt; - - before(() => { - originalGlobalIt = global.it; - - // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global - it = () => {}; - - /* - * These tests override `describe` and `it`, so we need to - * un-override them here so they won't interfere. - */ - originalRuleTesterDescribe = RuleTester.describe; - RuleTester.describe = void 0; - originalRuleTesterIt = RuleTester.it; - RuleTester.it = void 0; - }); - after(() => { - - // eslint-disable-next-line no-global-assign -- Restore Mocha global - it = originalGlobalIt; - RuleTester.describe = originalRuleTesterDescribe; - RuleTester.it = originalRuleTesterIt; - }); - beforeEach(() => { - ruleTester = new RuleTester(); - }); - - it("throws an error explaining that the current test framework does not support `only`", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - }, "The current test framework does not support exclusive tests with `only`."); - }); - }); - }); - - describe("test cases", () => { - const ruleName = "no-var"; - const rule = require("./fixtures/no-var"); - - let originalRuleTesterIt; - let spyRuleTesterIt; - let originalRuleTesterItOnly; - let spyRuleTesterItOnly; - - before(() => { - originalRuleTesterIt = RuleTester.it; - spyRuleTesterIt = sinon.spy(); - RuleTester.it = spyRuleTesterIt; - originalRuleTesterItOnly = RuleTester.itOnly; - spyRuleTesterItOnly = sinon.spy(); - RuleTester.itOnly = spyRuleTesterItOnly; - }); - after(() => { - RuleTester.it = originalRuleTesterIt; - RuleTester.itOnly = originalRuleTesterItOnly; - }); - beforeEach(() => { - spyRuleTesterIt.resetHistory(); - spyRuleTesterItOnly.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("isn't called for normal tests", () => { - ruleTester.run(ruleName, rule, { - valid: ["const notVar = 42;"], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;"); - sinon.assert.notCalled(spyRuleTesterItOnly); - }); - - it("calls it or itOnly for every test case", () => { - - /* - * `RuleTester` doesn't implement test case exclusivity itself. - * Setting `only: true` just causes `RuleTester` to call - * whatever `only()` function is provided by the test framework - * instead of the regular `it()` function. - */ - - ruleTester.run(ruleName, rule, { - valid: [ - "const valid = 42;", - { - code: "const onlyValid = 42;", - only: true - } - ], - invalid: [ - { - code: "var invalid = 42;", - errors: [/^Bad var/u] - }, - { - code: "var onlyInvalid = 42;", - errors: [/^Bad var/u], - only: true - } - ] - }); - - sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;"); - sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;"); - sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;"); - sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;"); - }); - }); - - describe("static helper wrapper", () => { - it("adds `only` to string test cases", () => { - const test = RuleTester.only("const valid = 42;"); - - assert.deepStrictEqual(test, { - code: "const valid = 42;", - only: true - }); - }); - - it("adds `only` to object test cases", () => { - const test = RuleTester.only({ code: "const valid = 42;" }); - - assert.deepStrictEqual(test, { - code: "const valid = 42;", - only: true - }); - }); - }); - }); - - it("should not throw an error when everything passes", () => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }); - - it("should throw an error when valid code is invalid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error when valid code is invalid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - { code: "eval(foo)" } - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error if invalid code is valid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "Eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have 1 error but had 0/u); - }); - - it("should throw an error when the error message is wrong", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [{ message: "Bad error message." }] } - ] - }); - }, assertErrorMatches("Bad var.", "Bad error message.")); - }); - - it("should throw an error when the error message regex does not match", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: [{ message: /Bad error message/u }] } - ] - }); - }, /Expected 'Bad var.' to match \/Bad error message\//u); - }); - - it("should throw an error when the error is not a supported type", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [42] } - ] - }); - }, /Error should be a string, object, or RegExp/u); - }); - - it("should throw an error when any of the errors is not a supported type", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar; var baz = quux", errors: [{ message: "Bad var.", type: "VariableDeclaration" }, null] } - ] - }); - }, /Error should be a string, object, or RegExp/u); - }); - - it("should throw an error when the error is a string and it does not match error message", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: ["Bad error message."] } - ] - }); - }, assertErrorMatches("Bad var.", "Bad error message.")); - }); - - it("should throw an error when the error is a string and it does not match error message", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - - valid: [ - ], - invalid: [ - { code: "var foo = bar;", errors: [/Bad error message/u] } - ] - }); - }, /Expected 'Bad var.' to match \/Bad error message\//u); - }); - - it("should not throw an error when the error is a string and it matches error message", () => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: ["Bad var."] } - ] - }); - }); - - it("should not throw an error when the error is a regex and it matches error message", () => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: [/^Bad var/u] } - ] - }); - }); - - it("should not throw an error when the error is a string and the suggestion fixer is failing", () => { - ruleTester.run("no-var", require("./fixtures/suggestions").withFailingFixer, { - valid: [], - invalid: [ - { code: "foo", errors: ["some message"] } - ] - }); - }); - - it("throws an error when the error is a string and the suggestion fixer provides a fix", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [ - { code: "foo", errors: ["Avoid using identifiers named 'foo'."] } - ] - }); - }, "Error at index 0 has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions."); - }); - - it("should throw an error when the error is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [{ Message: "Bad var." }] } - ] - }); - }, /Invalid error property name 'Message'/u); - }); - - it("should throw an error when any of the errors is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { - code: "var foo = bar; var baz = quux", - errors: [ - { message: "Bad var.", type: "VariableDeclaration" }, - { message: "Bad var.", typo: "VariableDeclaration" } - ] - } - ] - }); - }, /Invalid error property name 'typo'/u); - }); - - it("should not throw an error when the error is a regex in an object and it matches error message", () => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: [{ message: /^Bad var/u }] } - ] - }); - }); - - it("should throw an error when the expected output doesn't match", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration" }] } - ] - }); - }, /Output is incorrect/u); - }); - - it("should use strict equality to compare output", () => { - const replaceProgramWith5Rule = { - meta: { - fixable: "code" - }, - - create: context => ({ - Program(node) { - context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") }); - } - }) - }; - - // Should not throw. - ruleTester.run("foo", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - - assert.throws(() => { - ruleTester.run("foo", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: 5, errors: 1 } - ] - }); - }, /Output is incorrect/u); - }); - - it("should throw an error when the expected output doesn't match and errors is just a number", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: 1 } - ] - }); - }, /Output is incorrect/u); - }); - - it("should not throw an error when the expected output is null and no errors produce output", () => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "eval(x)", errors: 1, output: null }, - { code: "eval(x); eval(y);", errors: 2, output: null } - ] - }); - }); - - it("should throw an error when the expected output is null and problems produce output", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: null, errors: 1 } - ] - }); - }, /Expected no autofixes to be suggested/u); - - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { - code: "var foo = bar; var qux = boop;", - output: null, - errors: 2 - } - ] - }); - }, /Expected no autofixes to be suggested/u); - }); - - it("should throw an error when the expected output is null and only some problems produce output", () => { - assert.throws(() => { - ruleTester.run("fixes-one-problem", require("./fixtures/fixes-one-problem"), { - valid: [], - invalid: [ - { code: "foo", output: null, errors: 2 } - ] - }); - }, /Expected no autofixes to be suggested/u); - }); - - it("should throw an error when the expected output is not null and the output does not differ from the code", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-eval"), { - valid: [], - invalid: [ - { code: "eval('')", output: "eval('')", errors: 1 } - ] - }); - }, "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null."); - }); - - it("should throw an error when the expected output isn't specified and problems produce output", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - }, "The rule fixed the code. Please add 'output' property."); - }); - - it("should throw an error if invalid code specifies wrong type", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression2" }] } - ] - }); - }, /Error type should be CallExpression2, found CallExpression/u); - }); - - it("should throw an error if invalid code specifies wrong line", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 5 }] } - ] - }); - }, /Error line should be 5/u); - }); - - it("should not skip line assertion if line is a falsy value", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "\neval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 0 }] } - ] - }); - }, /Error line should be 0/u); - }); - - it("should throw an error if invalid code specifies wrong column", () => { - const wrongColumn = 10, - expectedErrorMessage = "Error column should be 1"; - - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{ - message: "eval sucks.", - column: wrongColumn - }] - }] - }); - }, expectedErrorMessage); - }); - - it("should throw error for empty error array", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [] - }] - }); - }, /Invalid cases must have at least one error/u); - }); - - it("should throw error for errors : 0", () => { - assert.throws(() => { - ruleTester.run( - "suggestions-messageIds", - require("./fixtures/suggestions") - .withMessageIds, - { - valid: [], - invalid: [ - { - code: "var foo;", - errors: 0 - } - ] - } - ); - }, /Invalid cases must have 'error' value greater than 0/u); - }); - - it("should not skip column assertion if column is a falsy value", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "var foo; eval(foo)", - errors: [{ message: "eval sucks.", column: 0 }] - }] - }); - }, /Error column should be 0/u); - }); - - it("should throw an error if invalid code specifies wrong endLine", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endLine: 10 }] } - ] - }); - }, "Error endLine should be 10"); - }); - - it("should throw an error if invalid code specifies wrong endColumn", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endColumn: 10 }] } - ] - }); - }, "Error endColumn should be 10"); - }); - - it("should throw an error if invalid code has the wrong number of errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { - code: "eval(foo)", - errors: [ - { message: "eval sucks.", type: "CallExpression" }, - { message: "eval sucks.", type: "CallExpression" } - ] - } - ] - }); - }, /Should have 2 errors but had 1/u); - }); - - it("should throw an error if invalid code does not have errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)" } - ] - }); - }, /Did not specify errors for an invalid test of no-eval/u); - }); - - it("should throw an error if invalid code has the wrong explicit number of errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: 2 } - ] - }); - }, /Should have 2 errors but had 1/u); - }); - - it("should throw an error if there's a parsing error in a valid test", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "1eval('foo')" - ], - invalid: [ - { code: "eval('foo')", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if there's a parsing error in an invalid test", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "noeval('foo')" - ], - invalid: [ - { code: "1eval('foo')", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - "noeval('foo')" - ], - invalid: [ - { code: "1eval('foo')", errors: 1 } - ] - }); - }, /fatal parsing error/iu); - }); - - // https://github.com/eslint/eslint/issues/4779 - it("should throw an error if there's a parsing error and output doesn't match", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [], - invalid: [ - { code: "obvious parser error", output: "string that doesnt match", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if an error object has no properties", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{}] - }] - }); - }, "Test error must specify either a 'messageId' or 'message'."); - }); - - it("should throw an error if an error has a property besides message or messageId", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{ line: 1 }] - }] - }); - }, "Test error must specify either a 'messageId' or 'message'."); - }); - - it("should pass-through the globals config of valid tests to the to rule", () => { - ruleTester.run("no-test-global", require("./fixtures/no-test-global"), { - valid: [ - "var test = 'foo'", - { - code: "var test2 = 'bar'", - globals: { test: true } - } - ], - invalid: [{ code: "bar", errors: 1 }] - }); - }); - - it("should pass-through the globals config of invalid tests to the to rule", () => { - ruleTester.run("no-test-global", require("./fixtures/no-test-global"), { - valid: ["var test = 'foo'"], - invalid: [ - { - code: "var test = 'foo'; var foo = 'bar'", - errors: 1 - }, - { - code: "var test = 'foo'", - globals: { foo: true }, - errors: [{ message: "Global variable foo should not be used." }] - } - ] - }); - }); - - it("should pass-through the settings config to rules", () => { - ruleTester.run("no-test-settings", require("./fixtures/no-test-settings"), { - valid: [ - { - code: "var test = 'bar'", settings: { test: 1 } - } - ], - invalid: [ - { - code: "var test = 'bar'", settings: { "no-test": 22 }, errors: 1 - } - ] - }); - }); - - it("should pass-through the filename to the rule", () => { - (function() { - ruleTester.run("", require("./fixtures/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "somefile.js" - } - ], - invalid: [ - { - code: "var foo = 'bar'", - errors: [ - { message: "Filename test was not defined." } - ] - } - ] - }); - }()); - }); - - it("should pass-through the options to the rule", () => { - ruleTester.run("no-invalid-args", require("./fixtures/no-invalid-args"), { - valid: [ - { - code: "var foo = 'bar'", - options: [false] - } - ], - invalid: [ - { - code: "var foo = 'bar'", - options: [true], - errors: [{ message: "Invalid args" }] - } - ] - }); - }); - - it("should throw an error if the options are an object", () => { - assert.throws(() => { - ruleTester.run("no-invalid-args", require("./fixtures/no-invalid-args"), { - valid: [ - { - code: "foo", - options: { ok: true } - } - ], - invalid: [] - }); - }, /options must be an array/u); - }); - - it("should throw an error if the options are a number", () => { - assert.throws(() => { - ruleTester.run("no-invalid-args", require("./fixtures/no-invalid-args"), { - valid: [ - { - code: "foo", - options: 0 - } - ], - invalid: [] - }); - }, /options must be an array/u); - }); - - it("should pass-through the parser to the rule", () => { - const spy = sinon.spy(ruleTester.linter, "verify"); - - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - { - code: "Eval(foo)" - } - ], - invalid: [ - { - code: "eval(foo)", - parser: require.resolve("esprima"), - errors: [{ message: "eval sucks.", line: 1 }] - } - ] - }); - assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima")); - }); - - // skipping because it's not something our parser cares about - it.skip("should pass normalized ecmaVersion to the rule", () => { - const reportEcmaVersionRule = { - meta: { - messages: { - ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}." - } - }, - create: context => ({ - Program(node) { - const { ecmaVersion } = context.parserOptions; - - context.report({ - node, - messageId: "ecmaVersionMessage", - data: { type: typeof ecmaVersion, ecmaVersion } - }); - } - }) - }; - - const notEspree = require.resolve("./fixtures/empty-program-parser"); - - ruleTester.run("report-ecma-version", reportEcmaVersionRule, { - valid: [], - invalid: [ - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }] - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parserOptions: {} - }, - { - code: "
", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parserOptions: { ecmaFeatures: { jsx: true } } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parser: require.resolve("espree") - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - parserOptions: { ecmaVersion: 6 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - parserOptions: { ecmaVersion: 2015 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - env: { browser: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - env: { es6: false } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - env: { es6: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }], - env: { es6: false, es2017: true } - }, - { - code: "let x", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - env: { es6: "truthy" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }], - env: { es2017: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }], - env: { es2020: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }], - env: { es2021: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parser: require.resolve("espree"), - parserOptions: { ecmaVersion: "latest" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } } - }, - { - code: "import 'foo'", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest", sourceType: "module" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest" }, - env: { es6: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest" }, - env: { es2020: true } - }, - - // Non-Espree parsers normalize ecmaVersion if it's not "latest" - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parser: notEspree - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parser: notEspree, - parserOptions: {} - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }], - parser: notEspree, - parserOptions: { ecmaVersion: 5 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - parser: notEspree, - parserOptions: { ecmaVersion: 6 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: 6 } }], - parser: notEspree, - parserOptions: { ecmaVersion: 2015 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }], - parser: notEspree, - parserOptions: { ecmaVersion: "latest" } - } - ] - }); - - [{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => { - new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, { - valid: [], - invalid: [ - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }] - }, - { - code: "", - parserOptions: {}, - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }] - } - ] - }); - }); - - new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, { - valid: [], - invalid: [ - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }] - }, - { - code: "", - parserOptions: { ecmaVersion: "latest" }, - errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }] - } - ] - }); - }); - - it("should pass-through services from parseForESLint to the rule", () => { - const enhancedParserPath = require.resolve("./fixtures/enhanced-parser"); - const disallowHiRule = { - create: context => ({ - Literal(node) { - const disallowed = context.parserServices.test.getMessage(); // returns "Hi!" - - if (node.value === disallowed) { - context.report({ node, message: `Don't use '${disallowed}'` }); - } - } - }) - }; - - ruleTester.run("no-hi", disallowHiRule, { - valid: [ - { - code: "'Hello!'", - parser: enhancedParserPath - } - ], - invalid: [ - { - code: "'Hi!'", - parser: enhancedParserPath, - errors: [{ message: "Don't use 'Hi!'" }] - } - ] - }); - }); - - it("should prevent invalid options schemas", () => { - assert.throws(() => { - ruleTester.run("no-invalid-schema", require("./fixtures/no-invalid-schema"), { - valid: [ - "var answer = 6 * 7;", - { code: "var answer = 6 * 7;", options: [] } - ], - invalid: [ - { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] } - ] - }); - }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf"); - - }); - - it("should prevent schema violations in options", () => { - assert.throws(() => { - ruleTester.run("no-schema-violation", require("./fixtures/no-schema-violation"), { - valid: [ - "var answer = 6 * 7;", - { code: "var answer = 6 * 7;", options: ["foo"] } - ], - invalid: [ - { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] } - ] - }); - }, /Value "bar" should be equal to one of the allowed values./u); - - }); - - it("should disallow invalid defaults in rules", () => { - const ruleWithInvalidDefaults = { - meta: { - schema: [ - { - oneOf: [ - { enum: ["foo"] }, - { - type: "object", - properties: { - foo: { - enum: ["foo", "bar"], - default: "foo" - } - }, - additionalProperties: false - } - ] - } - ] - }, - create: () => ({}) - }; - - assert.throws(() => { - ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, { - valid: [ - { - code: "foo", - options: [{}] - } - ], - invalid: [] - }); - }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u); - }); - - it("throw an error when an unknown config option is included", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - { code: "Eval(foo)", foo: "bar" } - ], - invalid: [] - }); - }, /ESLint configuration in rule-tester is invalid./u); - }); - - it("throw an error when an invalid config value is included", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("./fixtures/no-eval"), { - valid: [ - { code: "Eval(foo)", env: ["es6"] } - ], - invalid: [] - }); - }, /Property "env" is the wrong type./u); - }); - - it("should pass-through the tester config to the rule", () => { - ruleTester = new RuleTester({ - globals: { test: true } - }); - - ruleTester.run("no-test-global", require("./fixtures/no-test-global"), { - valid: [ - "var test = 'foo'", - "var test2 = test" - ], - invalid: [{ code: "bar", errors: 1, globals: { foo: true } }] - }); - }); - - it("should correctly set the globals configuration", () => { - const config = { globals: { test: true } }; - - RuleTester.setDefaultConfig(config); - assert( - RuleTester.getDefaultConfig().globals.test, - "The default config object is incorrect" - ); - }); - - it("should correctly reset the global configuration", () => { - const config = { globals: { test: true } }; - - RuleTester.setDefaultConfig(config); - RuleTester.resetDefaultConfig(); - assert.deepStrictEqual( - RuleTester.getDefaultConfig(), - { parser: require.resolve('@typescript-eslint/parser'), rules: {} }, - "The default configuration has not reset correctly" - ); - }); - - it("should enforce the global configuration to be an object", () => { - - /** - * Set the default config for the rules tester - * @param {Object} config configuration object - * @returns {Function} Function to be executed - * @private - */ - function setConfig(config) { - return function() { - RuleTester.setDefaultConfig(config); - }; - } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); - }); - - it("should pass-through the globals config to the tester then to the to rule", () => { - const config = { globals: { test: true } }; - - RuleTester.setDefaultConfig(config); - ruleTester = new RuleTester(); - - ruleTester.run("no-test-global", require("./fixtures/no-test-global"), { - valid: [ - "var test = 'foo'", - "var test2 = test" - ], - invalid: [{ code: "bar", errors: 1, globals: { foo: true } }] - }); - }); - - it("should throw an error if AST was modified", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if AST was modified (at Program)", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-first"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-first"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if AST was modified (at Program:exit)", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => { - const usesStartEndRule = { - create(context) { - return { - CallExpression(node) { - noop(node.arguments[1].start); - }, - "BinaryExpression[operator='+']"(node) { - noop(node.end); - }, - "UnaryExpression[operator='-']"(node) { - noop(context.sourceCode.getFirstToken(node).start); - }, - ConditionalExpression(node) { - noop(context.sourceCode.getFirstToken(node).end); - }, - BlockStatement(node) { - noop(context.sourceCode.getCommentsInside(node)[0].start); - }, - ObjectExpression(node) { - noop(context.sourceCode.getCommentsInside(node)[0].end); - }, - Decorator(node) { - noop(node.start); - } - }; - } - }; - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["foo(a, b)"], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }] - }); - }, "Use node.range[1] instead of node.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = -b * c;", errors: 1 }] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["var a = b ? c : d;"], - invalid: [] - }); - }, "Use token.range[1] instead of token.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["function f() { /* comment */ }"], - invalid: [] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }] - }); - }, "Use token.range[1] instead of token.end"); - - const enhancedParserPath = require.resolve("./fixtures/enhanced-parser"); - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "foo(a, b)", parser: enhancedParserPath }], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = b * (c + d) / e;", parser: enhancedParserPath, errors: 1 }] - }); - }, "Use node.range[1] instead of node.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = -b * c;", parser: enhancedParserPath, errors: 1 }] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "var a = b ? c : d;", parser: enhancedParserPath }], - invalid: [] - }); - }, "Use token.range[1] instead of token.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "function f() { /* comment */ }", parser: enhancedParserPath }], - invalid: [] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var x = //\n {\n //comment\n //\n}", parser: enhancedParserPath, errors: 1 }] - }); - }, "Use token.range[1] instead of token.end"); - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "@foo class A {}", parser: require.resolve("./fixtures/enhanced-parser2") }], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - }); - - it("should throw an error if no test scenarios given", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last")); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - }); - - it("should throw an error if no acceptable test scenario object is given", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), []); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), ""); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), 2); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), {}); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), { - valid: [] - }); - }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/modify-ast-at-last"), { - invalid: [] - }); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios"); - }); - - // Nominal message/messageId use cases - it("should assert match if message provided in both test and result.", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "something" }] }] - }); - }, /Avoid using variables named/u); - - ruleTester.run("foo", require("./fixtures/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }] - }); - }); - - it("should assert match between messageId if provided in both test and result.", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }] - }); - }, "messageId 'avoidFoo' does not match expected messageId 'unused'."); - - ruleTester.run("foo", require("./fixtures/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }); - it("should assert match between resulting message output if messageId and data provided in both test and result", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }] - }); - }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\""); - }); - - it("should throw if the message has a single unsubstituted placeholder when data is not specified", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMissingData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }, "The reported message has an unsubstituted placeholder 'name'. Please provide the missing value via the 'data' property in the context.report() call."); - }); - - it("should throw if the message has a single unsubstituted placeholders when data is specified", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMissingData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "name" } }] }] - }); - }, "Hydrated message \"Avoid using variables named 'name'.\" does not match \"Avoid using variables named '{{ name }}'."); - }); - - it("should throw if the message has multiple unsubstituted placeholders when data is not specified", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMultipleMissingDataProperties, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }, "The reported message has unsubstituted placeholders: 'type', 'name'. Please provide the missing values via the 'data' property in the context.report() call."); - }); - - it("should not throw if the data in the message contains placeholders not present in the raw message", () => { - ruleTester.run("foo", require("./fixtures/messageId").withPlaceholdersInData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }); - - it("should throw if the data in the message contains the same placeholder and data is not specified", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withSamePlaceholdersInData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }, "The reported message has an unsubstituted placeholder 'name'. Please provide the missing value via the 'data' property in the context.report() call."); - }); - - it("should not throw if the data in the message contains the same placeholder and data is specified", () => { - ruleTester.run("foo", require("./fixtures/messageId").withSamePlaceholdersInData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "{{ name }}" } }] }] - }); - }); - - it("should not throw an error for specifying non-string data values", () => { - ruleTester.run("foo", require("./fixtures/messageId").withNonStringData, { - valid: [], - invalid: [{ code: "0", errors: [{ messageId: "avoid", data: { value: 0 } }] }] - }); - }); - - // messageId/message misconfiguration cases - it("should throw if user tests for both message and messageId", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }] - }); - }, "Error should not specify both 'message' and a 'messageId'."); - }); - it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'"); - }); - it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }] - }); - }, /Invalid messageId 'useFoo'/u); - }); - it("should throw if data provided without messageId.", () => { - assert.throws(() => { - ruleTester.run("foo", require("./fixtures/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ data: "something" }] }] - }); - }, "Test error must specify either a 'messageId' or 'message'."); - }); - - describe("suggestions", () => { - it("should throw if suggestions are available but not specified", () => { - assert.throw(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ message: "Avoid using identifiers named 'foo'." }] - }] - }); - }, "Error at index 0 has suggestions. Please specify 'suggestions' property on the test error object."); - }); - - it("should pass with valid suggestions (tested using desc)", () => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }] - }] - }] - }); - }); - - it("should pass with suggestions on multiple lines", () => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [ - { - code: "function foo() {\n var foo = 1;\n}", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "function bar() {\n var foo = 1;\n}" - }] - }, { - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "function foo() {\n var bar = 1;\n}" - }] - }] - } - ] - }); - }); - - it("should pass with valid suggestions (tested using messageIds)", () => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should fail with valid suggestions when testing using both desc and messageIds for the same suggestion", () => { - assert.throw(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0: Test should not specify both 'desc' and 'messageId'."); - }); - - it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using messageIds and data)", () => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }); - - it("should fail with a single missing data placeholder when data is not specified", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMissingPlaceholderData, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }] - }] - }] - }); - }, "The message of the suggestion has an unsubstituted placeholder 'newName'. Please provide the missing value via the 'data' property for the suggestion in the context.report() call."); - }); - - it("should fail with a single missing data placeholder when data is specified", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMissingPlaceholderData, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - data: { other: "name" }, - output: "var bar;" - }] - }] - }] - }); - }, "The message of the suggestion has an unsubstituted placeholder 'newName'. Please provide the missing value via the 'data' property for the suggestion in the context.report() call."); - }); - - it("should fail with multiple missing data placeholders when data is not specified", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMultipleMissingPlaceholderDataProperties, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "rename", - output: "var bar;" - }] - }] - }] - }); - }, "The message of the suggestion has unsubstituted placeholders: 'currentName', 'newName'. Please provide the missing values via the 'data' property for the suggestion in the context.report() call."); - }); - - - it("should fail when tested using empty suggestion test objects even if the array length is correct", () => { - assert.throw(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{}, {}] - }] - }] - }); - }, "Error Suggestion at index 0: Test must specify either 'messageId' or 'desc'"); - }); - - it("should fail when tested using non-empty suggestion test objects without an output property", () => { - assert.throw(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ messageId: "renameFoo" }, {}] - }] - }] - }); - }, 'Error Suggestion at index 0: The "output" property is required.'); - }); - - it("should support explicitly expecting no suggestions", () => { - [void 0, null, false, []].forEach(suggestions => { - ruleTester.run("suggestions-basic", require("./fixtures/no-eval"), { - valid: [], - invalid: [{ - code: "eval('var foo');", - errors: [{ - message: "eval sucks.", - suggestions - }] - }] - }); - }); - }); - - it("should fail when expecting no suggestions and there are suggestions", () => { - [void 0, null, false, []].forEach(suggestions => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions - }] - }] - }); - }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\""); - }); - }); - - it("should fail when testing for suggestions that don't exist", () => { - assert.throws(() => { - ruleTester.run("no-var", require("./fixtures/no-var"), { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - message: "Bad var.", - messageId: "this-does-not-exist" - }] - }] - }] - }); - }, 'Error should have suggestions on error with message: "Bad var."'); - }); - - it("should support specifying only the amount of suggestions", () => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: 1 - }] - }] - }); - }); - - it("should fail when there are a different number of suggestions", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: 2 - }] - }] - }); - }, "Error should have 2 suggestions. Instead found 1 suggestions"); - }); - - it("should fail when there are a different number of suggestions for arrays", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }, "Error should have 2 suggestions. Instead found 1 suggestions"); - }); - - it("should fail when the suggestion property is neither a number nor an array", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: "1" - }] - }] - }); - }, "Test error object property 'suggestions' should be an array or a number"); - }); - - it("should throw if suggestion fix made a syntax error.", () => { - assert.throw(() => { - ruleTester.run( - "foo", - { - meta: { hasSuggestions: true }, - create(context) { - return { - Identifier(node) { - context.report({ - node, - message: "make a syntax error", - suggest: [ - { - desc: "make a syntax error", - fix(fixer) { - return fixer.replaceText(node, "one two"); - } - } - ] - }); - } - }; - } - }, - { - valid: [""], - invalid: [{ - code: "one()", - errors: [{ - message: "make a syntax error", - suggestions: [{ - desc: "make a syntax error", - output: "one two()" - }] - }] - }] - } - ); - }, /A fatal parsing error occurred in suggestion fix\.\nError: .+\nSuggestion output:\n.+/u); - }); - - it("should throw if the suggestion description doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "not right", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead."); - }); - - it("should pass when different suggestion matchers use desc and messageId", () => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should throw if the suggestion messageId doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "unused", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0: messageId should be 'unused' but got 'renameFoo' instead."); - }); - - it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }] - }] - }] - }); - }, "Error Suggestion at index 0: Test can not use 'messageId' if rule under test doesn't define 'meta.messages'."); - }); - - it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "removeFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1: Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo']."); - }); - - it("should throw if hydrated desc doesn't match (wrong data value)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - data: { newName: "car" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0: Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\"."); - }); - - it("should throw if hydrated desc doesn't match (wrong data key)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { name: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1: Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\"."); - }); - - it("should throw if test specifies both desc and data", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0: Test should not specify both 'desc' and 'data'."); - }); - - it("should throw if test uses data but doesn't specify messageId", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - messageId: "avoidFoo", - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1: Test must specify 'messageId' if 'data' is used."); - }); - - it("should throw if the resulting suggestion output doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var baz;" - }] - }] - }] - }); - }, "Expected the applied suggestion fix to match the test suggestion output"); - }); - - it("should throw if the resulting suggestion output is the same as the original source code", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").withFixerWithoutChanges, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var foo;" - }] - }] - }] - }); - }, "The output of a suggestion should differ from the original source code for suggestion at index: 0 on error with message: \"Avoid using identifiers named 'foo'.\""); - }); - - it("should fail when specified suggestion isn't an object", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "Avoid using identifiers named 'foo'.", - suggestions: [null] - }] - }] - }); - }, "Test suggestion in 'suggestions' array must be an object."); - - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - message: "avoidFoo", - suggestions: [ - { - messageId: "renameFoo", - output: "var bar;" - }, - "Rename identifier 'foo' to 'baz'" - ] - }] - }] - }); - }, "Test suggestion in 'suggestions' array must be an object."); - }); - - it("should fail when the suggestion is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("./fixtures/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ - message: "avoidFoo", - suggestions: [{ - message: "Rename identifier 'foo' to 'bar'" - }] - }] - }] - }); - }, /Invalid suggestion property name 'message'/u); - }); - - it("should fail when any of the suggestions is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("./fixtures/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "renameFoo", - outpt: "var baz;" - }] - }] - }] - }); - }, /Invalid suggestion property name 'outpt'/u); - }); - - it("should fail if a rule produces two suggestions with the same description", () => { - assert.throws(() => { - ruleTester.run("suggestions-with-duplicate-descriptions", require("../../fixtures/testers/rule-tester/suggestions").withDuplicateDescriptions, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - }, "Suggestion message 'Rename 'foo' to 'bar'' reported from suggestion 1 was previously reported by suggestion 0. Suggestion messages should be unique within an error."); - }); - - it("should fail if a rule produces two suggestions with the same messageId without data", () => { - assert.throws(() => { - ruleTester.run("suggestions-with-duplicate-messageids-no-data", require("../../fixtures/testers/rule-tester/suggestions").withDuplicateMessageIdsNoData, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - }, "Suggestion message 'Rename identifier' reported from suggestion 1 was previously reported by suggestion 0. Suggestion messages should be unique within an error."); - }); - - it("should fail if a rule produces two suggestions with the same messageId with data", () => { - assert.throws(() => { - ruleTester.run("suggestions-with-duplicate-messageids-with-data", require("../../fixtures/testers/rule-tester/suggestions").withDuplicateMessageIdsWithData, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - }, "Suggestion message 'Rename identifier 'foo' to 'bar'' reported from suggestion 1 was previously reported by suggestion 0. Suggestion messages should be unique within an error."); - }); - - it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => { - assert.throws(() => { - ruleTester.run("suggestions-missing-hasSuggestions-property", require("./fixtures/suggestions").withoutHasSuggestionsProperty, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); - }); - }); - - describe("deprecations", () => { - let processStub; - const ruleWithNoSchema = { - meta: { - type: "suggestion" - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - const ruleWithNoMeta = { - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - beforeEach(() => { - processStub = sinon.stub(process, "emitWarning"); - }); - - afterEach(() => { - processStub.restore(); - }); - - it("should log a deprecation warning when using the legacy function-style API for rule", () => { - - /** - * Legacy-format rule (a function instead of an object with `create` method). - * @param {RuleContext} context The ESLint rule context object. - * @returns {Object} Listeners. - */ - function functionStyleRule(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - - ruleTester.run("function-style-rule", functionStyleRule, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"function-style-rule\" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when meta is not defined for the rule", () => { - ruleTester.run("rule-with-no-meta-1", ruleWithNoMeta, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-no-meta-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when schema is not defined for the rule", () => { - ruleTester.run("rule-with-no-schema-1", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-no-schema-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when schema is `undefined`", () => { - const ruleWithUndefinedSchema = { - meta: { - type: "problem", - // eslint-disable-next-line no-undefined -- intentionally added for test case - schema: undefined - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-undefined-schema", ruleWithUndefinedSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-undefined-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when schema is `null`", () => { - const ruleWithNullSchema = { - meta: { - type: "problem", - schema: null - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-null-schema", ruleWithNullSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-null-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should not log a deprecation warning when schema is an empty array", () => { - const ruleWithEmptySchema = { - meta: { - type: "suggestion", - schema: [] - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-no-options", ruleWithEmptySchema, { - valid: [], - invalid: [{ code: "var foo = bar;", errors: 1 }] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule is an object-style rule, the legacy rule API warning is not emitted", () => { - ruleTester.run("rule-with-no-schema-2", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule has meta.schema and there are test cases with options, the missing schema warning is not emitted", () => { - const ruleWithSchema = { - meta: { - type: "suggestion", - schema: [{ - type: "boolean" - }] - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-schema", ruleWithSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [true], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule does not have meta, but there are no test cases with options, the missing schema warning is not emitted", () => { - ruleTester.run("rule-with-no-meta-2", ruleWithNoMeta, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule has meta without meta.schema, but there are no test cases with options, the missing schema warning is not emitted", () => { - ruleTester.run("rule-with-no-schema-3", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - it("When the rule has meta without meta.schema, and some test cases have options property but it's an empty array, the missing schema warning is not emitted", () => { - ruleTester.run("rule-with-no-schema-4", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - }); - - /** - * Asserts that a particular value will be emitted from an EventEmitter. - * @param {EventEmitter} emitter The emitter that should emit a value - * @param {string} emitType The type of emission to listen for - * @param {any} expectedValue The value that should be emitted - * @returns {Promise