diff --git a/.eslintrc.json b/.eslintrc.json index 2e3271fe..77dd0d9f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,8 @@ "eslint:recommended", "plugin:eslint-plugin/recommended", "plugin:n/recommended", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "plugin:jsdoc/recommended" ], "rules": { "no-var": "error", @@ -22,6 +23,15 @@ ], "eslint-plugin/prefer-placeholders": "error", "eslint-plugin/test-case-shorthand-strings": "error", - "prettier/prettier": "error" + "prettier/prettier": "error", + "jsdoc/check-types": "off", + "jsdoc/no-undefined-types": "off", + "jsdoc/require-jsdoc": "off", + "jsdoc/require-param-description": "off", + "jsdoc/require-property-description": "off", + "jsdoc/require-returns-description": "off", + "jsdoc/require-yields": "off", + "jsdoc/tag-lines": ["warn", "never", { "startLines": 1 }], + "jsdoc/valid-types": "off" } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79db47f9..3f7d2f8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,26 @@ jobs: - name: ▶️ Run lint script run: npm run lint + types: + name: 🌋 Types + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: ⎔ Setup Node + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: 📥 Install dependencies + run: npm ci + + - name: ▶️ Typecheck + uses: gozala/typescript-error-reporter-action@v1.0.9 + with: + error_fail_threshold: 160 + test: name: 🧪 Test (Node@${{ matrix.node }} - ESLint@${{ matrix.eslint }} - ${{ diff --git a/.gitignore b/.gitignore index 384a20d1..3f34c465 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ node_modules .idea coverage/ .eslintcache + +# Generated types +/rules/**/*.d.ts +/rules/**/*.d.ts.map +/index.d.ts +/index.d.ts.map diff --git a/__tests__/rule-tester.js b/__tests__/rule-tester.js index 057a31ed..17821047 100644 --- a/__tests__/rule-tester.js +++ b/__tests__/rule-tester.js @@ -1,5 +1,5 @@ /** - * @fileoverview Helpers for tests. + * @file Helpers for tests. * @author 唯然 */ 'use strict' diff --git a/declaration.tsconfig.json b/declaration.tsconfig.json new file mode 100644 index 00000000..405b8da5 --- /dev/null +++ b/declaration.tsconfig.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig", + "exclude": ["__tests__/**/*"], + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "noEmit": false, + "emitDeclarationOnly": true + } +} diff --git a/package-lock.json b/package-lock.json index c7b3742c..edb7b736 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,18 @@ { "name": "eslint-plugin-promise", - "version": "0.0.0-semantically-released", + "version": "7.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "eslint-plugin-promise", - "version": "0.0.0-semantically-released", + "version": "7.0.0", "license": "ISC", "devDependencies": { + "@types/eslint": "^9.6.0", + "@types/estree": "^1.0.5", + "@types/jest": "^29.5.12", + "@types/node": "^18.19.42", "@typescript-eslint/parser": "^7.17.0", "doctoc": "^2.2.1", "eslint": "^8.56.0", @@ -16,6 +20,7 @@ "eslint-doc-generator": "^1.7.1", "eslint-plugin-eslint-plugin": "^6.2.0", "eslint-plugin-jest": "^28.6.0", + "eslint-plugin-jsdoc": "^48.8.3", "eslint-plugin-n": "^17.9.0", "eslint-plugin-prettier": "^5.2.1", "globals": "^15.8.0", @@ -24,7 +29,7 @@ "lint-staged": "^15.2.7", "npm-run-all2": "^6.2.2", "prettier": "^3.3.3", - "typescript": "~5.5.4" + "typescript": "~5.5.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -627,6 +632,21 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1358,6 +1378,24 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -1391,6 +1429,17 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1407,10 +1456,11 @@ } }, "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "18.19.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", + "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -1742,6 +1792,16 @@ "node": ">= 8" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2299,6 +2359,16 @@ "node": ">=14" } }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2424,10 +2494,11 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -2792,6 +2863,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -3194,6 +3272,31 @@ "eslint": "^8.56.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.8.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.8.3.tgz", + "integrity": "sha512-AtIvwwW9D17MRkM0Z0y3/xZYaa9mdAvJrkY6fU/HNUwGbmMtHVvK4qRM9CDixGVtfNrQitb8c6zQtdh6cTOvLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.46.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.5", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-plugin-n": { "version": "17.9.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.9.0.tgz", @@ -3357,10 +3460,11 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -5119,6 +5223,16 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6223,6 +6337,20 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-imports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6815,10 +6943,11 @@ } }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -6927,6 +7056,13 @@ "node": ">=8" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -6974,6 +7110,31 @@ "source-map": "^0.6.0" } }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7479,6 +7640,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index f6003dc7..48566285 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,20 @@ "Aadit M Shah (https://aadit.codes/)" ], "scripts": { + "build:0": "run-s clean", + "build:1-declaration": "tsc -p declaration.tsconfig.json", + "build": "run-s build:*", + "check": "run-s clean && run-p check:*", + "clean:declarations-lib": "rm -rf $(find rules -type f -name '*.d.ts*')", + "clean:declarations-top": "rm -rf $(find . -maxdepth 1 -type f -name '*.d.ts*')", + "clean": "run-p clean:*", "format": "prettier --write . && eslint . --fix", - "lint": "npm-run-all \"lint:*\"", "lint:eslint-docs": "npm run update:eslint-docs && git diff --exit-code", "lint:js": "eslint --report-unused-disable-directives .", + "skip:lint:tsc": "tsc", + "lint": "run-s clean && run-p lint:*", "prepare": "husky", + "skip:prepublishOnly": "run-s build", "test": "jest --coverage", "update:eslint-docs": "eslint-doc-generator && npm run format" }, @@ -66,6 +75,10 @@ ] }, "devDependencies": { + "@types/eslint": "^9.6.0", + "@types/estree": "^1.0.5", + "@types/jest": "^29.5.12", + "@types/node": "^18.19.42", "@typescript-eslint/parser": "^7.17.0", "doctoc": "^2.2.1", "eslint": "^8.56.0", @@ -73,6 +86,7 @@ "eslint-doc-generator": "^1.7.1", "eslint-plugin-eslint-plugin": "^6.2.0", "eslint-plugin-jest": "^28.6.0", + "eslint-plugin-jsdoc": "^48.8.3", "eslint-plugin-n": "^17.9.0", "eslint-plugin-prettier": "^5.2.1", "globals": "^15.8.0", @@ -81,7 +95,7 @@ "lint-staged": "^15.2.7", "npm-run-all2": "^6.2.2", "prettier": "^3.3.3", - "typescript": "~5.5.4" + "typescript": "~5.5.3" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" diff --git a/rules/always-return.js b/rules/always-return.js index 093ea33a..973c5d09 100644 --- a/rules/always-return.js +++ b/rules/always-return.js @@ -9,13 +9,13 @@ const getDocsUrl = require('./lib/get-docs-url') * @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression * @typedef {import('eslint').Rule.CodePath} CodePath * @typedef {import('eslint').Rule.CodePathSegment} CodePathSegment + * @typedef { (FunctionExpression | ArrowFunctionExpression) & { parent: CallExpression }} InlineThenFunctionExpression */ /** - * @typedef { (FunctionExpression | ArrowFunctionExpression) & { parent: CallExpression }} InlineThenFunctionExpression + * @param {Node} node + * @returns {boolean} */ - -/** @param {Node} node */ function isFunctionWithBlockStatement(node) { if (node.type === 'FunctionExpression') { return true @@ -41,7 +41,10 @@ function isMemberCall(memberName, node) { ) } -/** @param {Node} node */ +/** + * @param {Node} node + * @returns {boolean} + */ function isFirstArgument(node) { return Boolean( node.parent && node.parent.arguments && node.parent.arguments[0] === node, @@ -62,7 +65,9 @@ function isInlineThenFunctionExpression(node) { /** * Checks whether the given node is the last `then()` callback in a promise chain. + * * @param {InlineThenFunctionExpression} node + * @returns {boolean} */ function isLastCallback(node) { /** @type {Node} */ @@ -124,7 +129,7 @@ function peek(arr) { return arr[arr.length - 1] } -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'problem', docs: { @@ -156,7 +161,6 @@ module.exports = { * executing branches ("codePathSegment"s) within the given function * @property {Record} branchInfoMap This is an object representing information * about all branches within the given function - * * @typedef {object} BranchInfo * @property {boolean} good This is a boolean representing whether * the given branch explicitly `return`s or `throw`s. It starts as `false` @@ -168,24 +172,25 @@ module.exports = { /** * funcInfoStack is a stack representing the stack of currently executing - * functions + * functions * example: - * funcInfoStack = [ { branchIDStack: [ 's1_1' ], - * branchInfoMap: - * { s1_1: - * { good: false, - * loc: } } }, - * { branchIDStack: ['s2_1', 's2_4'], - * branchInfoMap: - * { s2_1: - * { good: false, - * loc: }, - * s2_2: - * { good: true, - * loc: }, - * s2_4: - * { good: false, - * loc: } } } ] + * funcInfoStack = [ { branchIDStack: [ 's1_1' ], + * branchInfoMap: + * { s1_1: + * { good: false, + * loc: } } }, + * { branchIDStack: ['s2_1', 's2_4'], + * branchInfoMap: + * { s2_1: + * { good: false, + * loc: }, + * s2_2: + * { good: true, + * loc: }, + * s2_4: + * { good: false, + * loc: } } } ] + * * @type {FuncInfo[]} */ const funcInfoStack = [] @@ -257,4 +262,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/avoid-new.js b/rules/avoid-new.js index 71cf4f7c..fdbaa15a 100644 --- a/rules/avoid-new.js +++ b/rules/avoid-new.js @@ -7,7 +7,7 @@ const getDocsUrl = require('./lib/get-docs-url') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -29,4 +29,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/catch-or-return.js b/rules/catch-or-return.js index c12b82fc..b6c38099 100644 --- a/rules/catch-or-return.js +++ b/rules/catch-or-return.js @@ -10,7 +10,7 @@ const getDocsUrl = require('./lib/get-docs-url') const isPromise = require('./lib/is-promise') const isMemberCallWithObjectName = require('./lib/is-member-call-with-object-name') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'problem', docs: { @@ -125,4 +125,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/lib/has-promise-callback.js b/rules/lib/has-promise-callback.js index 7b6b71ca..9a2ecc1c 100644 --- a/rules/lib/has-promise-callback.js +++ b/rules/lib/has-promise-callback.js @@ -10,16 +10,12 @@ * @typedef {import('estree').SimpleCallExpression} CallExpression * @typedef {import('estree').MemberExpression} MemberExpression * @typedef {import('estree').Identifier} Identifier - * * @typedef {object} NameIsThenOrCatch * @property {'then' | 'catch'} name - * * @typedef {object} PropertyIsThenOrCatch * @property {Identifier & NameIsThenOrCatch} property - * * @typedef {object} CalleeIsPromiseCallback * @property {MemberExpression & PropertyIsThenOrCatch} callee - * * @typedef {CallExpression & CalleeIsPromiseCallback} HasPromiseCallback */ /** @@ -28,8 +24,8 @@ */ function hasPromiseCallback(node) { // istanbul ignore if -- only being called within `CallExpression` - if (node.type !== 'CallExpression') return - if (node.callee.type !== 'MemberExpression') return + if (node.type !== 'CallExpression') return false + if (node.callee.type !== 'MemberExpression') return false const propertyName = node.callee.property.name return propertyName === 'then' || propertyName === 'catch' } diff --git a/rules/lib/is-member-call-with-object-name b/rules/lib/is-member-call-with-object-name.js similarity index 92% rename from rules/lib/is-member-call-with-object-name rename to rules/lib/is-member-call-with-object-name.js index 60107e02..3a30725b 100644 --- a/rules/lib/is-member-call-with-object-name +++ b/rules/lib/is-member-call-with-object-name.js @@ -2,7 +2,7 @@ /** * @param {string} objectName - * @param {Node} node + * @param {import('estree').Node} node * @returns {node is CallExpression} */ function isMemberCallWithObjectName(objectName, node) { diff --git a/rules/lib/is-promise-constructor.js b/rules/lib/is-promise-constructor.js index cbf3d3d3..d898f544 100644 --- a/rules/lib/is-promise-constructor.js +++ b/rules/lib/is-promise-constructor.js @@ -10,13 +10,12 @@ * @typedef {import('estree').NewExpression} NewExpression * @typedef {import('estree').FunctionExpression} FunctionExpression * @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression - * * @typedef {NewExpression & { callee: { type: 'Identifier', name: 'Promise' } }} NewPromise * @typedef {NewPromise & { arguments: [FunctionExpression | ArrowFunctionExpression] }} NewPromiseWithInlineExecutor - * */ /** * Checks whether the given node is new Promise(). + * * @param {Node} node * @returns {node is NewPromise} */ @@ -30,6 +29,7 @@ function isPromiseConstructor(node) { /** * Checks whether the given node is new Promise(() => {}). + * * @param {Node} node * @returns {node is NewPromiseWithInlineExecutor} */ diff --git a/rules/no-callback-in-promise.js b/rules/no-callback-in-promise.js index bc786749..75a0979b 100644 --- a/rules/no-callback-in-promise.js +++ b/rules/no-callback-in-promise.js @@ -13,7 +13,7 @@ const isCallback = require('./lib/is-callback') const CB_BLACKLIST = ['callback', 'cb', 'next', 'done'] -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -68,4 +68,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-multiple-resolved.js b/rules/no-multiple-resolved.js index 37e1ab0a..644a6ed9 100644 --- a/rules/no-multiple-resolved.js +++ b/rules/no-multiple-resolved.js @@ -29,11 +29,13 @@ const { /** * An expression that can throw an error. * see https://github.com/eslint/eslint/blob/e940be7a83d0caea15b64c1e1c2785a6540e2641/lib/linter/code-path-analysis/code-path-analyzer.js#L639-L643 + * * @typedef {CallExpression | MemberExpression | NewExpression | ImportExpression | YieldExpression} ThrowableExpression */ /** * Iterate all previous path segments. + * * @param {CodePathSegment} segment * @returns {Iterable} */ @@ -63,6 +65,7 @@ function* iterateAllPrevPathSegments(segment) { } /** * Iterate all next path segments. + * * @param {CodePathSegment} segment * @returns {Iterable} */ @@ -93,6 +96,7 @@ function* iterateAllNextPathSegments(segment) { /** * Finds the same route path from the given path following previous path segments. + * * @param {CodePathSegment} segment * @returns {CodePathSegment | null} */ @@ -172,6 +176,7 @@ class CodePathInfo { /** * Check all paths and return paths resolved multiple times. + * * @param {PromiseCodePathContext} promiseCodePathContext * @returns {Iterable} */ @@ -195,6 +200,7 @@ class CodePathInfo { } /** * Compute the previously resolved path. + * * @param {CodePathSegment} segment * @param {PromiseCodePathContext} promiseCodePathContext * @returns {AlreadyResolvedData | null} @@ -330,17 +336,20 @@ class PromiseCodePathContext { /** @type {Set} */ this.resolvedSegmentIds = new Set() } - /** @param {CodePathSegment} */ + /** @param {CodePathSegment} segment */ addResolvedTryBlockCodePathSegment(segment) { this.resolvedSegmentIds.add(segment.id) } - /** @param {CodePathSegment} */ + /** + * @param {CodePathSegment} segment + * @returns {boolean} + */ isResolvedTryBlockCodePathSegment(segment) { return this.resolvedSegmentIds.has(segment.id) } } -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'problem', docs: { @@ -356,7 +365,6 @@ module.exports = { }, schema: [], }, - /** @param {import('eslint').Rule.RuleContext} context */ create(context) { const reported = new Set() const promiseCodePathContext = new PromiseCodePathContext() @@ -407,7 +415,10 @@ module.exports = { /** @type {Set} */ const resolverReferences = new Set() const resolvers = node.params.filter( - /** @returns {node is Identifier} */ + /** + * @param {import('estree').Pattern} node + * @returns {node is Identifier} + */ (node) => node && node.type === 'Identifier', ) for (const resolver of resolvers) { @@ -479,7 +490,6 @@ module.exports = { onUnreachableCodePathSegmentEnd(segment) { codePathInfoStack[0].onSegmentExit(segment) }, - /** @type {Identifier} */ 'CallExpression > Identifier.callee'(node) { const codePathInfo = codePathInfoStack[0] const resolverReferences = resolverReferencesStack[0] @@ -498,4 +508,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-native.js b/rules/no-native.js index 373c7f91..47876ed2 100644 --- a/rules/no-native.js +++ b/rules/no-native.js @@ -24,7 +24,7 @@ function isDeclared(scope, ref) { }) } -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -74,4 +74,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-nesting.js b/rules/no-nesting.js index 2a95cdd7..b99c71a2 100644 --- a/rules/no-nesting.js +++ b/rules/no-nesting.js @@ -10,7 +10,7 @@ const getDocsUrl = require('./lib/get-docs-url') const hasPromiseCallback = require('./lib/has-promise-callback') const isInsidePromise = require('./lib/is-inside-promise') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -26,6 +26,7 @@ module.exports = { /** * Array of callback function scopes. * Scopes are in order closest to the current node. + * * @type {import('eslint').Scope.Scope[]} */ const callbackScopes = [] @@ -120,4 +121,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-new-statics.js b/rules/no-new-statics.js index c2483ce4..00f02ed2 100644 --- a/rules/no-new-statics.js +++ b/rules/no-new-statics.js @@ -3,7 +3,7 @@ const PROMISE_STATICS = require('./lib/promise-statics') const getDocsUrl = require('./lib/get-docs-url') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'problem', docs: { @@ -39,4 +39,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-promise-in-callback.js b/rules/no-promise-in-callback.js index fefd22e3..2aa30b96 100644 --- a/rules/no-promise-in-callback.js +++ b/rules/no-promise-in-callback.js @@ -10,7 +10,7 @@ const getDocsUrl = require('./lib/get-docs-url') const isPromise = require('./lib/is-promise') const isInsideCallback = require('./lib/is-inside-callback') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -43,4 +43,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-return-in-finally.js b/rules/no-return-in-finally.js index d9e3db8e..9c56bbb7 100644 --- a/rules/no-return-in-finally.js +++ b/rules/no-return-in-finally.js @@ -3,7 +3,7 @@ const getDocsUrl = require('./lib/get-docs-url') const isPromise = require('./lib/is-promise') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'problem', docs: { @@ -47,4 +47,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/no-return-wrap.js b/rules/no-return-wrap.js index 5ab2ccb9..f7c73841 100644 --- a/rules/no-return-wrap.js +++ b/rules/no-return-wrap.js @@ -1,3 +1,5 @@ +// @ts-check + /** * Rule: no-return-wrap function * Prevents unnecessary wrapping of results in Promise.resolve @@ -35,7 +37,7 @@ function isInPromise(context, node) { return functionNode && functionNode.parent && isPromise(functionNode.parent) } -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -65,8 +67,9 @@ module.exports = { /** * Checks a call expression, reporting if necessary. - * @param callExpression The call expression. - * @param node The node to report. + * + * @param {import('estree').CallExpression} callExpression The call expression. + * @param {import('estree').Node} node The node to report. */ function checkCallExpression({ callee }, node) { if ( @@ -93,4 +96,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/param-names.js b/rules/param-names.js index 5d4500a6..c7414b34 100644 --- a/rules/param-names.js +++ b/rules/param-names.js @@ -5,7 +5,7 @@ const { isPromiseConstructorWithInlineExecutor, } = require('./lib/is-promise-constructor') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -71,4 +71,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/prefer-await-to-callbacks.js b/rules/prefer-await-to-callbacks.js index 65b411ef..7117175b 100644 --- a/rules/prefer-await-to-callbacks.js +++ b/rules/prefer-await-to-callbacks.js @@ -3,7 +3,7 @@ const { getAncestors } = require('./lib/eslint-compat') const getDocsUrl = require('./lib/get-docs-url') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -94,4 +94,4 @@ module.exports = { ArrowFunctionExpression: checkLastParamsForCallback, } }, -} +}) diff --git a/rules/prefer-await-to-then.js b/rules/prefer-await-to-then.js index 09532477..0bdae208 100644 --- a/rules/prefer-await-to-then.js +++ b/rules/prefer-await-to-then.js @@ -9,7 +9,7 @@ const { getAncestors, getScope } = require('./lib/eslint-compat') const getDocsUrl = require('./lib/get-docs-url') const isMemberCallWithObjectName = require('./lib/is-member-call-with-object-name') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'suggestion', docs: { @@ -32,7 +32,12 @@ module.exports = { }, }, create(context) { - /** Returns true if node is inside yield or await expression. */ + /** + * Returns true if node is inside yield or await expression. + * + * @param {import('estree').Node} node + * @returns {boolean} + */ function isInsideYieldOrAwait(node) { return getAncestors(context, node).some((parent) => { return ( @@ -54,6 +59,9 @@ module.exports = { * Returns true if node is created at the top-level scope. * Await statements are not allowed at the top level, * only within function declarations. + * + * @param {import('estree').Node} node + * @returns {boolean} */ function isTopLevelScoped(node) { return getScope(context, node).block.type === 'Program' @@ -87,4 +95,4 @@ module.exports = { }, } }, -} +}) diff --git a/rules/valid-params.js b/rules/valid-params.js index f06c40ea..c8e2d34b 100644 --- a/rules/valid-params.js +++ b/rules/valid-params.js @@ -3,7 +3,7 @@ const getDocsUrl = require('./lib/get-docs-url') const isPromise = require('./lib/is-promise') -module.exports = { +module.exports = /** @satisfies {import('eslint').Rule.RuleModule} */ ({ meta: { type: 'problem', docs: { @@ -73,4 +73,4 @@ module.exports = { }, } }, -} +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..81bad450 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,44 @@ +{ + "files": ["index.js"], + "include": ["rules/**/*", "__tests__/**/*"], + "compilerOptions": { + "lib": ["es2022"], + "target": "es2022", + "types": ["node", "jest"], + + "module": "node16", + "moduleResolution": "node16", + + "strict": true, + + "skipLibCheck": false, // See https://github.com/voxpelli/tsconfig/issues/1 + + /* Clean up generated declarations */ + "removeComments": false, + "stripInternal": true, + + /* Make it a JS-targeted config */ + "allowJs": true, + "checkJs": true, // Set to true to check all js files + "noEmit": true, + "resolveJsonModule": true, + + /* Additional type checks */ + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + + /* Additional non-type checks */ + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": false, // Deactivated as I believe implicit "return undefined" at end of function is okay + explicit clashes with ESLint "no-useless-return" + "noUnusedLocals": true, + "noUnusedParameters": true + + /* To make strict checking somewhat less strict during a transition stage, add one or more of: */ + // "noImplicitThis": false, + // "noImplicitAny": false, + // "strictNullChecks": false + } +}