From 2397ef789b4046e1e0858082cbcda2f882a86179 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 24 Jun 2020 12:36:33 -0400 Subject: [PATCH 01/19] added basic packages + airbnb --- .eslintrc.json | 251 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 11 ++- 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..3a9077160 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,251 @@ +{ + "env": { + "es2020": true, + "node": true + }, + "extends": [ + "airbnb" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + // "accessor-pairs": "error", + // "array-bracket-newline": "error", + // "array-bracket-spacing": "error", + // "array-callback-return": "error", + // "array-element-newline": "error", + // "arrow-body-style": "error", + // "arrow-parens": "error", + // "arrow-spacing": "error", + // "block-scoped-var": "error", + // "block-spacing": "error", + // "brace-style": "error", + // "callback-return": "error", + // "camelcase": "error", + // "capitalized-comments": "error", + // "class-methods-use-this": "error", + // "comma-dangle": "error", + // "comma-spacing": "error", + // "comma-style": "error", + // "complexity": "error", + // "computed-property-spacing": "error", + // "consistent-return": "error", + // "consistent-this": "error", + // "curly": "error", + // "default-case": "error", + // "default-case-last": "error", + // "default-param-last": "error", + // "dot-location": "error", + // "dot-notation": "error", + // "eol-last": "error", + // "eqeqeq": "error", + // "func-call-spacing": "error", + // "func-name-matching": "error", + // "func-names": "error", + // "func-style": "error", + // "function-call-argument-newline": "error", + // "function-paren-newline": "error", + // "generator-star-spacing": "error", + // "global-require": "error", + // "grouped-accessor-pairs": "error", + // "guard-for-in": "error", + // "handle-callback-err": "error", + // "id-blacklist": "error", + // "id-length": "error", + // "id-match": "error", + // "implicit-arrow-linebreak": "error", + // "indent": "error", + // "indent-legacy": "error", + // "init-declarations": "error", + // "jsx-quotes": "error", + // "key-spacing": "error", + // "keyword-spacing": "error", + // "line-comment-position": "error", + // "linebreak-style": "error", + // "lines-around-comment": "error", + // "lines-around-directive": "error", + // "lines-between-class-members": "error", + // "max-classes-per-file": "error", + // "max-depth": "error", + // "max-len": "error", + // "max-lines": "error", + // "max-lines-per-function": "error", + // "max-nested-callbacks": "error", + // "max-params": "error", + // "max-statements": "error", + // "max-statements-per-line": "error", + // "multiline-comment-style": "error", + // "multiline-ternary": "error", + // "new-cap": "error", + // "new-parens": "error", + // "newline-after-var": "error", + // "newline-before-return": "error", + // "newline-per-chained-call": "error", + // "no-alert": "error", + // "no-array-constructor": "error", + // "no-await-in-loop": "error", + // "no-bitwise": "error", + // "no-buffer-constructor": "error", + // "no-caller": "error", + // "no-catch-shadow": "error", + // "no-confusing-arrow": "error", + // "no-console": "error", + // "no-constructor-return": "error", + // "no-continue": "error", + // "no-div-regex": "error", + // "no-duplicate-imports": "error", + // "no-else-return": "error", + // "no-empty-function": "error", + // "no-eq-null": "error", + // "no-eval": "error", + // "no-extend-native": "error", + // "no-extra-bind": "error", + // "no-extra-label": "error", + // "no-extra-parens": "error", + // "no-floating-decimal": "error", + // "no-implicit-coercion": "error", + // "no-implicit-globals": "error", + // "no-implied-eval": "error", + // "no-inline-comments": "error", + // "no-invalid-this": "error", + // "no-iterator": "error", + // "no-label-var": "error", + // "no-labels": "error", + // "no-lone-blocks": "error", + // "no-lonely-if": "error", + // "no-loop-func": "error", + // "no-loss-of-precision": "error", + // "no-magic-numbers": "error", + // "no-mixed-operators": "error", + // "no-mixed-requires": "error", + // "no-multi-assign": "error", + // "no-multi-spaces": "error", + // "no-multi-str": "error", + // "no-multiple-empty-lines": "error", + // "no-native-reassign": "error", + // "no-negated-condition": "error", + // "no-negated-in-lhs": "error", + // "no-nested-ternary": "error", + // "no-new": "error", + // "no-new-func": "error", + // "no-new-object": "error", + // "no-new-require": "error", + // "no-new-wrappers": "error", + // "no-octal-escape": "error", + // "no-param-reassign": "error", + // "no-path-concat": "error", + // "no-plusplus": "error", + // "no-process-env": "error", + // "no-process-exit": "error", + // "no-promise-executor-return": "error", + // "no-proto": "error", + // "no-restricted-exports": "error", + // "no-restricted-globals": "error", + // "no-restricted-imports": "error", + // "no-restricted-modules": "error", + // "no-restricted-properties": "error", + // "no-restricted-syntax": "error", + // "no-return-assign": "error", + // "no-return-await": "error", + // "no-script-url": "error", + // "no-self-compare": "error", + // "no-sequences": "error", + // "no-shadow": "error", + // "no-spaced-func": "error", + // "no-sync": "error", + // "no-tabs": "error", + // "no-template-curly-in-string": "error", + // "no-ternary": "error", + // "no-throw-literal": "error", + // "no-trailing-spaces": "error", + // "no-undef-init": "error", + // "no-undefined": "error", + // "no-underscore-dangle": "error", + // "no-unmodified-loop-condition": "error", + // "no-unneeded-ternary": "error", + // "no-unreachable-loop": "error", + // "no-unused-expressions": "error", + // "no-use-before-define": "error", + // "no-useless-backreference": "error", + // "no-useless-call": "error", + // "no-useless-computed-key": "error", + // "no-useless-concat": "error", + // "no-useless-constructor": "error", + // "no-useless-rename": "error", + // "no-useless-return": "error", + // "no-var": "error", + // "no-void": "error", + // "no-warning-comments": "error", + // "no-whitespace-before-property": "error", + // "nonblock-statement-body-position": "error", + // "object-curly-newline": "error", + // "object-curly-spacing": "error", + // "object-property-newline": "error", + // "object-shorthand": "error", + // "one-var": "error", + // "one-var-declaration-per-line": "error", + // "operator-assignment": "error", + // "operator-linebreak": "error", + // "padded-blocks": "error", + // "padding-line-between-statements": "error", + // "prefer-arrow-callback": "error", + // "prefer-const": "error", + // "prefer-destructuring": "error", + // "prefer-exponentiation-operator": "error", + // "prefer-named-capture-group": "error", + // "prefer-numeric-literals": "error", + // "prefer-object-spread": "error", + // "prefer-promise-reject-errors": "error", + // "prefer-reflect": "error", + // "prefer-regex-literals": "error", + // "prefer-rest-params": "error", + // "prefer-spread": "error", + // "prefer-template": "error", + // "quote-props": "error", + // "quotes": "error", + // "radix": "error", + // "require-atomic-updates": "error", + // "require-await": "error", + // "require-jsdoc": "error", + // "require-unicode-regexp": "error", + // "rest-spread-spacing": "error", + // "semi": "error", + // "semi-spacing": "error", + // "semi-style": "error", + // "sort-imports": "error", + // "sort-keys": "error", + // "sort-vars": "error", + // "space-before-blocks": "error", + // "space-before-function-paren": "error", + // "space-in-parens": "error", + // "space-infix-ops": "error", + // "space-unary-ops": "error", + // "spaced-comment": "error", + // "strict": "error", + // "switch-colon-spacing": "error", + // "symbol-description": "error", + // "template-curly-spacing": "error", + // "template-tag-spacing": "error", + // "unicode-bom": "error", + // "valid-jsdoc": "error", + // "vars-on-top": "error", + // "wrap-iife": "error", + // "wrap-regex": "error", + // "yield-star-spacing": "error", + // "yoda": "error" + + }, + + "settings": { + "react": { + "version": "999.999.999" + } + } + +} diff --git a/package.json b/package.json index 2631589d8..17d790c70 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "prepare": "npm run build", "build": "tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", - "lint": "tslint --project .", + "lint": "eslint src/**/*.ts", "test-lint": "tslint --project tsconfig.test.json \"src/**/*.spec.ts\" && tslint --project tsconfig.test.json \"src/test-helpers.ts\"", "mocha": "TS_NODE_PROJECT=tsconfig.test.json nyc mocha --config .mocharc.json \"src/**/*.spec.ts\"", "test": "npm run lint && npm run test-lint && npm run mocha && npm run test:types", @@ -59,8 +59,17 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.6", "@types/sinon": "^7.0.11", + "@typescript-eslint/eslint-plugin": "^3.4.0", + "@typescript-eslint/parser": "^3.4.0", "chai": "^4.2.0", "codecov": "^3.2.0", + "eslint": "^7.3.1", + "eslint-config-airbnb": "^18.2.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-import": "^2.21.2", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-react": "^7.20.0", "mocha": "^6.1.4", "nyc": "^14.0.0", "rewiremock": "^3.13.4", From 15d5037cb5033ba065e21b8bbaeff80b7c818b83 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 24 Jun 2020 12:53:36 -0400 Subject: [PATCH 02/19] updated packages --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 17d790c70..8a07fdb01 100644 --- a/package.json +++ b/package.json @@ -60,16 +60,19 @@ "@types/mocha": "^5.2.6", "@types/sinon": "^7.0.11", "@typescript-eslint/eslint-plugin": "^3.4.0", + "@typescript-eslint/eslint-plugin-tslint": "^3.4.0", "@typescript-eslint/parser": "^3.4.0", "chai": "^4.2.0", "codecov": "^3.2.0", - "eslint": "^7.3.1", + "eslint": "^7.2.0", "eslint-config-airbnb": "^18.2.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.21.2", + "eslint-plugin-jsdoc": "^28.5.1", "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "^7.20.0", + "eslint-plugin-react-hooks": "^4.0.0", "mocha": "^6.1.4", "nyc": "^14.0.0", "rewiremock": "^3.13.4", From 6f06c285e0b0dbeee22cb331d95da8c09c6a6f04 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 24 Jun 2020 13:09:49 -0400 Subject: [PATCH 03/19] package updates --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 8a07fdb01..1915a09ec 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "codecov": "^3.2.0", "eslint": "^7.2.0", "eslint-config-airbnb": "^18.2.0", + "eslint-config-airbnb-base": "^14.2.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.21.2", "eslint-plugin-jsdoc": "^28.5.1", From 37feb1314e0d0bdae721a2385343096dee53b38a Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 24 Jun 2020 13:25:59 -0400 Subject: [PATCH 04/19] updated packages --- package.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/package.json b/package.json index 1915a09ec..e5b6fdb62 100644 --- a/package.json +++ b/package.json @@ -65,15 +65,9 @@ "chai": "^4.2.0", "codecov": "^3.2.0", "eslint": "^7.2.0", - "eslint-config-airbnb": "^18.2.0", "eslint-config-airbnb-base": "^14.2.0", - "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.21.2", - "eslint-plugin-jsdoc": "^28.5.1", - "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-react": "^7.20.0", - "eslint-plugin-react-hooks": "^4.0.0", "mocha": "^6.1.4", "nyc": "^14.0.0", "rewiremock": "^3.13.4", From 338b18cedeccc263dddec8055ccef71da1a23c8d Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 24 Jun 2020 14:17:23 -0400 Subject: [PATCH 05/19] mostly done, waiting on external dep updates --- .eslintrc.js | 1412 +++++++++++++++++++++++++++++++++++ .eslintrc.json | 251 ------- .vscode/settings.json | 19 +- package.json | 1 + src/App.spec.ts | 72 +- tslint-to-eslint-config.log | 55 ++ 6 files changed, 1521 insertions(+), 289 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json create mode 100644 tslint-to-eslint-config.log diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..3eb4db1b4 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,1412 @@ +/* +👋 Hi! This file was autogenerated by tslint-to-eslint-config. +https://github.com/typescript-eslint/tslint-to-eslint-config + +It represents the closest reasonable ESLint configuration to this +project's original TSLint configuration. + +We recommend eventually switching this configuration to extend from +the recommended rulesets in typescript-eslint. +https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md + +Happy linting! 💖 +*/ +module.exports = { + "env": { + "es6": true, + "node": true + }, + "extends": [ + "airbnb-base" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "tsconfig.json", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint", + "@typescript-eslint/tslint" + ], + "rules": { + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/class-name-casing": "error", + "@typescript-eslint/consistent-type-assertions": "error", + "@typescript-eslint/consistent-type-definitions": "error", + "@typescript-eslint/explicit-member-accessibility": [ + "error", + { + "accessibility": "explicit" + } + ], + "@typescript-eslint/indent": [ + "error", + 2, + { + "CallExpression": { + "arguments": "first" + }, + "ArrayExpression": "first", + "ObjectExpression": "first", + "FunctionDeclaration": { + "parameters": "first" + }, + "FunctionExpression": { + "parameters": "first" + } + } + ], + "@typescript-eslint/member-delimiter-style": [ + "error", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ], + "@typescript-eslint/no-empty-function": "error", + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-for-in-array": "error", + "@typescript-eslint/no-param-reassign": "error", + "@typescript-eslint/no-require-imports": "error", + "@typescript-eslint/no-this-alias": "error", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", + "@typescript-eslint/no-unused-expressions": "error", + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/quotes": [ + "error", + "single", + { + "avoidEscape": true + } + ], + "@typescript-eslint/semi": [ + "error", + "always" + ], + "@typescript-eslint/strict-boolean-expressions": "error", + "@typescript-eslint/type-annotation-spacing": "error", + "accessor-pairs": "off", + "array-bracket-newline": [ + "off", + "consistent" + ], + "array-bracket-spacing": [ + "error", + "never" + ], + "array-callback-return": [ + "error", + { + "allowImplicit": true, + "checkForEach": false + } + ], + "array-element-newline": [ + "off", + { + "multiline": true, + "minItems": 3 + } + ], + "arrow-body-style": [ + "error", + "as-needed", + { + "requireReturnForObjectLiteral": false + } + ], + "arrow-parens": [ + "off", + "always" + ], + "arrow-spacing": [ + "error", + { + "before": true, + "after": true + } + ], + "block-scoped-var": "error", + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "error", + "1tbs" + ], + "callback-return": "off", + "camelcase": "error", + "capitalized-comments": [ + "off", + "never", + { + "line": { + "ignorePattern": ".*", + "ignoreInlineComments": true, + "ignoreConsecutiveComments": true + }, + "block": { + "ignorePattern": ".*", + "ignoreInlineComments": true, + "ignoreConsecutiveComments": true + } + } + ], + "class-methods-use-this": [ + "error", + { + "exceptMethods": [] + } + ], + "comma-dangle": [ + "error", + "always-multiline" + ], + "comma-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "comma-style": [ + "error", + "last", + { + "exceptions": { + "ArrayExpression": false, + "ArrayPattern": false, + "ArrowFunctionExpression": false, + "CallExpression": false, + "FunctionDeclaration": false, + "FunctionExpression": false, + "ImportDeclaration": false, + "ObjectExpression": false, + "ObjectPattern": false, + "VariableDeclaration": false, + "NewExpression": false + } + } + ], + "complexity": [ + "off", + 11 + ], + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "error", + "consistent-this": "off", + "constructor-super": "error", + "curly": [ + "error", + "multi-line" + ], + "default-case": [ + "error", + { + "commentPattern": "^no default$" + } + ], + "default-case-last": "off", + "default-param-last": "off", + "dot-location": [ + "error", + "property" + ], + "dot-notation": [ + "error", + { + "allowKeywords": true, + "allowPattern": "" + } + ], + "eol-last": "error", + "eqeqeq": [ + "error", + "smart" + ], + "for-direction": "error", + "func-call-spacing": [ + "error", + "never" + ], + "func-name-matching": [ + "off", + "always", + { + "includeCommonJSModuleExports": false, + "considerPropertyDescriptor": true + } + ], + "func-names": "warn", + "func-style": [ + "off", + "expression" + ], + "function-call-argument-newline": [ + "off", + "consistent" + ], + "function-paren-newline": [ + "error", + "consistent" + ], + "generator-star-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "getter-return": [ + "error", + { + "allowImplicit": true + } + ], + "global-require": "error", + "grouped-accessor-pairs": "off", + "guard-for-in": "error", + "handle-callback-err": "off", + "id-blacklist": [ + "error", + "any", + "Number", + "number", + "String", + "string", + "Boolean", + "boolean", + "Undefined", + "undefined" + ], + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": [ + "error", + "beside" + ], + "import/default": "off", + "import/dynamic-import-chunkname": [ + "off", + { + "importFunctions": [], + "webpackChunknameFormat": "[0-9a-zA-Z-_/.]+" + } + ], + "import/export": "error", + "import/exports-last": "off", + "import/extensions": "off", + // "import/extensions": [ + // "error", + // "ignorePackages", + // { + // "js": "never", + // // "mjs": "never", + // "ts": "never" + // } + // ], + "import/first": "error", + "import/group-exports": "off", + "import/imports-first": "off", + "import/max-dependencies": [ + "off", + { + "max": 10 + } + ], + "import/named": "error", + "import/namespace": "off", + "import/newline-after-import": "error", + "import/no-absolute-path": "error", + "import/no-amd": "error", + "import/no-anonymous-default-export": [ + "off", + { + "allowArray": false, + "allowArrowFunction": false, + "allowAnonymousClass": false, + "allowAnonymousFunction": false, + "allowLiteral": false, + "allowObject": false + } + ], + "import/no-commonjs": "off", + // "import/no-cycle": [ + // "error", + // { + // "maxDepth": null, + // "ignoreExternal": false + // } + // ], + "import/no-default-export": "off", + "import/no-deprecated": "off", + "import/no-duplicates": "error", + "import/no-dynamic-require": "error", + "import/no-extraneous-dependencies": "error", + "import/no-internal-modules": "error", + "import/no-mutable-exports": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-named-default": "error", + "import/no-named-export": "off", + "import/no-namespace": "off", + "import/no-nodejs-modules": "off", + "import/no-relative-parent-imports": "off", + "import/no-restricted-paths": "off", + "import/no-self-import": "error", + "import/no-unassigned-import": "off", + "import/no-unresolved": "off", + // "import/no-unresolved": [ + // "error", + // { + // "commonjs": true, + // "caseSensitive": true + // } + // ], + "import/no-unused-modules": [ + "off", + { + "ignoreExports": [], + "missingExports": true, + "unusedExports": true + } + ], + "import/no-useless-path-segments": [ + "error", + { + "commonjs": true + } + ], + "import/no-webpack-loader-syntax": "error", + "import/order": [ + "error", + { + "groups": [ + [ + "builtin", + "external", + "internal" + ] + ] + } + ], + "import/prefer-default-export": "error", + "import/unambiguous": "off", + "indent": [ + "error", + 2, + { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "FunctionDeclaration": { + "parameters": 1, + "body": 1 + }, + "FunctionExpression": { + "parameters": 1, + "body": 1 + }, + "CallExpression": { + "arguments": 1 + }, + "ArrayExpression": 1, + "ObjectExpression": 1, + "ImportDeclaration": 1, + "flatTernaryExpressions": false, + "ignoredNodes": [ + "JSXElement", + "JSXElement > *", + "JSXAttribute", + "JSXIdentifier", + "JSXNamespacedName", + "JSXMemberExpression", + "JSXSpreadAttribute", + "JSXExpressionContainer", + "JSXOpeningElement", + "JSXClosingElement", + "JSXFragment", + "JSXOpeningFragment", + "JSXClosingFragment", + "JSXText", + "JSXEmptyExpression", + "JSXSpreadChild" + ], + "ignoreComments": false, + "offsetTernaryExpressions": false + } + ], + "init-declarations": "off", + // "jsdoc/check-alignment": "error", + // "jsdoc/check-indentation": "error", + // "jsdoc/newline-after-description": "error", + // "jsdoc/no-types": "error", + // "jsx-quotes": [ + // "off", + // "prefer-double" + // ], + "key-spacing": [ + "error", + { + "beforeColon": false, + "afterColon": true + } + ], + "keyword-spacing": [ + "error", + { + "before": true, + "after": true, + "overrides": { + "return": { + "after": true + }, + "throw": { + "after": true + }, + "case": { + "after": true + } + } + } + ], + "line-comment-position": [ + "off", + { + "position": "above", + "ignorePattern": "", + "applyDefaultPatterns": true + } + ], + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "off", + "lines-around-directive": [ + "error", + { + "before": "always", + "after": "always" + } + ], + "lines-between-class-members": [ + "error", + "always", + { + "exceptAfterSingleLine": false + } + ], + "max-classes-per-file": [ + "error", + 1 + ], + "max-depth": [ + "off", + 4 + ], + "max-len": [ + "error", + { + "code": 120 + } + ], + "max-lines": [ + "off", + { + "max": 300, + "skipBlankLines": true, + "skipComments": true + } + ], + "max-lines-per-function": [ + "off", + { + "max": 50, + "skipBlankLines": true, + "skipComments": true, + "IIFEs": true + } + ], + "max-nested-callbacks": "off", + "max-params": [ + "off", + 3 + ], + "max-statements": [ + "off", + 10 + ], + "max-statements-per-line": [ + "off", + { + "max": 1 + } + ], + "multiline-comment-style": [ + "off", + "starred-block" + ], + "multiline-ternary": [ + "off", + "never" + ], + "new-cap": [ + "error", + { + "newIsCap": true, + "newIsCapExceptions": [], + "capIsNew": false, + "capIsNewExceptions": [ + "Immutable.Map", + "Immutable.Set", + "Immutable.List" + ], + "properties": true + } + ], + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": [ + "error", + { + "ignoreChainWithDepth": 4 + } + ], + "no-alert": "warn", + "no-array-constructor": "error", + "no-async-promise-executor": "error", + "no-await-in-loop": "error", + "no-bitwise": "error", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-case-declarations": "error", + "no-catch-shadow": "off", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": [ + "error", + "always" + ], + "no-confusing-arrow": [ + "error", + { + "allowParens": true + } + ], + "no-console": "warn", + "no-const-assign": "error", + "no-constant-condition": "warn", + "no-constructor-return": "off", + "no-continue": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-div-regex": "off", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "off", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "error", + "no-else-return": [ + "error", + { + "allowElseIf": false + } + ], + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-function": [ + "error", + { + "allow": [ + "arrowFunctions", + "functions", + "methods" + ] + } + ], + "no-empty-pattern": "error", + "no-eq-null": "off", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-boolean-cast": "error", + "no-extra-label": "error", + "no-extra-parens": [ + "off", + "all", + { + "conditionalAssign": true, + "nestedBinaryExpressions": false, + "returnAssign": false, + "ignoreJSX": "all", + "enforceForArrowConditionals": false + } + ], + "no-extra-semi": "error", + "no-fallthrough": "error", + "no-floating-decimal": "error", + "no-func-assign": "error", + "no-global-assign": [ + "error", + { + "exceptions": [] + } + ], + "no-implicit-coercion": [ + "off", + { + "boolean": false, + "number": true, + "string": true, + "allow": [] + } + ], + "no-implicit-globals": "off", + "no-implied-eval": "error", + "no-import-assign": "off", + "no-inline-comments": "off", + "no-inner-declarations": "error", + "no-invalid-regexp": "error", + "no-invalid-this": "off", + "no-irregular-whitespace": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": [ + "error", + { + "allowLoop": false, + "allowSwitch": false + } + ], + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-loss-of-precision": "off", + "no-magic-numbers": [ + "off", + { + "ignore": [], + "ignoreArrayIndexes": true, + "enforceConst": true, + "detectObjects": false + } + ], + "no-misleading-character-class": "error", + "no-mixed-operators": [ + "error", + { + "groups": [ + [ + "%", + "**" + ], + [ + "%", + "+" + ], + [ + "%", + "-" + ], + [ + "%", + "*" + ], + [ + "%", + "/" + ], + [ + "/", + "*" + ], + [ + "&", + "|", + "<<", + ">>", + ">>>" + ], + [ + "==", + "!=", + "===", + "!==" + ], + [ + "&&", + "||" + ] + ], + "allowSamePrecedence": false + } + ], + "no-mixed-requires": [ + "off", + false + ], + "no-mixed-spaces-and-tabs": "error", + "no-multi-assign": "error", + "no-multi-spaces": [ + "error", + { + "ignoreEOLComments": false + } + ], + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-native-reassign": "off", + "no-negated-condition": "off", + "no-negated-in-lhs": "off", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-symbol": "error", + "no-new-wrappers": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-param-reassign": [ + "error", + { + "props": true, + "ignorePropertyModificationsFor": [ + "acc", + "accumulator", + "e", + "ctx", + "context", + "req", + "request", + "res", + "response", + "$scope", + "staticContext" + ] + } + ], + "no-path-concat": "error", + "no-plusplus": "error", + "no-process-env": "off", + "no-process-exit": "off", + "no-proto": "error", + "no-prototype-builtins": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-restricted-exports": [ + "off", + { + "restrictedNamedExports": [ + "default", + "then" + ] + } + ], + "no-restricted-globals": [ + "error", + "isFinite", + "isNaN", + "addEventListener", + "blur", + "close", + "closed", + "confirm", + "defaultStatus", + "defaultstatus", + "event", + "external", + "find", + "focus", + "frameElement", + "frames", + "history", + "innerHeight", + "innerWidth", + "length", + "location", + "locationbar", + "menubar", + "moveBy", + "moveTo", + "name", + "onblur", + "onerror", + "onfocus", + "onload", + "onresize", + "onunload", + "open", + "opener", + "opera", + "outerHeight", + "outerWidth", + "pageXOffset", + "pageYOffset", + "parent", + "print", + "removeEventListener", + "resizeBy", + "resizeTo", + "screen", + "screenLeft", + "screenTop", + "screenX", + "screenY", + "scroll", + "scrollbars", + "scrollBy", + "scrollTo", + "scrollX", + "scrollY", + "self", + "status", + "statusbar", + "stop", + "toolbar", + "top" + ], + "no-restricted-imports": [ + "off", + { + "paths": [], + "patterns": [] + } + ], + "no-restricted-modules": "off", + "no-restricted-properties": [ + "error", + { + "object": "arguments", + "property": "callee", + "message": "arguments.callee is deprecated" + }, + { + "object": "global", + "property": "isFinite", + "message": "Please use Number.isFinite instead" + }, + { + "object": "self", + "property": "isFinite", + "message": "Please use Number.isFinite instead" + }, + { + "object": "window", + "property": "isFinite", + "message": "Please use Number.isFinite instead" + }, + { + "object": "global", + "property": "isNaN", + "message": "Please use Number.isNaN instead" + }, + { + "object": "self", + "property": "isNaN", + "message": "Please use Number.isNaN instead" + }, + { + "object": "window", + "property": "isNaN", + "message": "Please use Number.isNaN instead" + }, + { + "property": "__defineGetter__", + "message": "Please use Object.defineProperty instead." + }, + { + "property": "__defineSetter__", + "message": "Please use Object.defineProperty instead." + }, + { + "object": "Math", + "property": "pow", + "message": "Use the exponentiation operator (**) instead." + } + ], + "no-restricted-syntax": [ + "error", + { + "selector": "ForInStatement", + "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." + }, + { + "selector": "ForOfStatement", + "message": "iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations." + }, + { + "selector": "LabeledStatement", + "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." + }, + { + "selector": "WithStatement", + "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." + } + ], + "no-return-assign": [ + "error", + "always" + ], + "no-return-await": "error", + "no-script-url": "error", + "no-self-assign": [ + "error", + { + "props": true + } + ], + "no-self-compare": "error", + "no-sequences": "error", + "no-setter-return": "off", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sparse-arrays": "error", + "no-sync": "off", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-this-before-super": "error", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef": "error", + "no-undef-init": "error", + "no-undefined": "off", + "no-underscore-dangle": "error", + "no-unexpected-multiline": "error", + "no-unmodified-loop-condition": "off", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": false + } + ], + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": false, + "allowTernary": false, + "allowTaggedTemplates": false + } + ], + "no-unused-labels": "error", + "no-unused-vars": "off", + // "no-unused-vars": [ + // "error", + // { + // "vars": "all", + // "args": "after-used", + // "ignoreRestSiblings": true + // } + // ], + "no-use-before-define": [ + "error", + { + "functions": true, + "classes": true, + "variables": true + } + ], + "no-useless-backreference": "off", + "no-useless-call": "off", + "no-useless-catch": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-escape": "error", + "no-useless-rename": [ + "error", + { + "ignoreDestructuring": false, + "ignoreImport": false, + "ignoreExport": false + } + ], + "no-useless-return": "error", + "no-var": "error", + "no-void": "error", + "no-warning-comments": [ + "off", + { + "terms": [ + "todo", + "fixme", + "xxx" + ], + "location": "start" + } + ], + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": [ + "error", + "beside", + { + "overrides": {} + } + ], + "object-curly-newline": [ + "error", + { + "ObjectExpression": { + "minProperties": 4, + "multiline": true, + "consistent": true + }, + "ObjectPattern": { + "minProperties": 4, + "multiline": true, + "consistent": true + }, + "ImportDeclaration": { + "minProperties": 4, + "multiline": true, + "consistent": true + }, + "ExportDeclaration": { + "minProperties": 4, + "multiline": true, + "consistent": true + } + } + ], + "object-curly-spacing": [ + "error", + "always" + ], + "object-property-newline": [ + "error", + { + "allowAllPropertiesOnSameLine": true, + "allowMultiplePropertiesPerLine": false + } + ], + "object-shorthand": "error", + "one-var": [ + "error", + "never" + ], + "one-var-declaration-per-line": [ + "error", + "always" + ], + "operator-assignment": [ + "error", + "always" + ], + "operator-linebreak": [ + "error", + "before", + { + "overrides": { + "=": "none" + } + } + ], + "padded-blocks": [ + "error", + { + "blocks": "never", + "classes": "never", + "switches": "never" + }, + { + "allowSingleLineBlocks": true + } + ], + "padding-line-between-statements": "off", + "prefer-arrow-callback": [ + "error", + { + "allowNamedFunctions": false, + "allowUnboundThis": true + } + ], + "prefer-const": "error", + "prefer-destructuring": [ + "error", + { + "VariableDeclarator": { + "array": false, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": false + } + }, + { + "enforceForRenamedProperties": false + } + ], + "prefer-exponentiation-operator": "off", + "prefer-named-capture-group": "off", + "prefer-numeric-literals": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": [ + "error", + { + "allowEmptyReject": true + } + ], + "prefer-reflect": "off", + "prefer-regex-literals": "off", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + "quote-props": [ + "error", + "as-needed" + ], + "quotes": [ + "error", + "single", + { + "avoidEscape": true + } + ], + "radix": "error", + "require-atomic-updates": "off", + "require-await": "off", + "require-jsdoc": "off", + "require-unicode-regexp": "off", + "require-yield": "error", + "rest-spread-spacing": [ + "error", + "never" + ], + "semi": [ + "error", + "always" + ], + "semi-spacing": [ + "error", + { + "before": false, + "after": true + } + ], + "semi-style": [ + "error", + "last" + ], + "sort-imports": [ + "off", + { + "ignoreCase": false, + "ignoreDeclarationSort": false, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": [ + "none", + "all", + "multiple", + "single" + ] + } + ], + "sort-keys": [ + "off", + "asc", + { + "caseSensitive": false, + "natural": true + } + ], + "sort-vars": "off", + "space-before-blocks": "error", + "space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never" + } + ], + "space-in-parens": [ + "error", + "never" + ], + "space-infix-ops": "error", + "space-unary-ops": [ + "error", + { + "words": true, + "nonwords": false, + "overrides": {} + } + ], + "spaced-comment": [ + "error", + "always", + { + "markers": [ + "/" + ] + } + ], + "strict": [ + "error", + "never" + ], + "switch-colon-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "symbol-description": "error", + "template-curly-spacing": "error", + "template-tag-spacing": [ + "error", + "never" + ], + "unicode-bom": [ + "error", + "never" + ], + "use-isnan": "error", + "valid-jsdoc": "off", + "valid-typeof": [ + "error", + { + "requireStringLiterals": true + } + ], + "vars-on-top": "error", + "wrap-iife": [ + "error", + "outside", + { + "functionPrototypeMethods": false + } + ], + "wrap-regex": "off", + "yield-star-spacing": [ + "error", + "after" + ], + "yoda": "error", + "@typescript-eslint/tslint/config": [ + "error", + { + "rules": { + "array-bracket-spacing": [ + true, + "never" + ], + "block-spacing": true, + "brace-style": [ + true, + "1tbs", + { + "allowSingleLine": true + } + ], + "function-name": [ + true, + { + "function-regex": {}, + "method-regex": {}, + "private-method-regex": {}, + "protected-method-regex": {}, + "static-method-regex": {} + } + ], + "import-name": true, + "no-dynamic-delete": true, + "no-else-after-return": true, + "no-function-constructor-with-string-args": true, + "no-increment-decrement": true, + "object-curly-spacing": [ + true, + "always" + ], + "object-shorthand-properties-first": true, + "prefer-array-literal": true, + "space-in-parens": [ + true, + "never" + ], + "ter-arrow-parens": [ + true, + "as-needed", + { + "requireForBlockBody": true + } + ], + "ter-computed-property-spacing": true, + "ter-func-call-spacing": true, + "ter-indent": [ + true, + 2, + { + "SwitchCase": 1 + } + ], + "ter-prefer-arrow-callback": true, + "typedef": [ + true, + "call-signature" + ], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-preblock", + "check-type", + "check-module", + "check-separator", + "check-rest-spread", + "check-typecast", + "check-type-operator" + ] + } + } + ] + }, + "settings": { + "react": { + "version": "999.999.999" + }, + "import/resolver": { + "node": { + "extensions": [ + ".mjs", + ".js", + ".json" + ] + } + }, + "import/extensions": [ + ".js", + // ".mjs", + ".ts" + ], + "import/core-modules": [], + "import/ignore": [ + "node_modules", + "\\.(coffee|scss|css|less|hbs|svg|json)$" + ] + } +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 3a9077160..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,251 +0,0 @@ -{ - "env": { - "es2020": true, - "node": true - }, - "extends": [ - "airbnb" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 11, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - // "accessor-pairs": "error", - // "array-bracket-newline": "error", - // "array-bracket-spacing": "error", - // "array-callback-return": "error", - // "array-element-newline": "error", - // "arrow-body-style": "error", - // "arrow-parens": "error", - // "arrow-spacing": "error", - // "block-scoped-var": "error", - // "block-spacing": "error", - // "brace-style": "error", - // "callback-return": "error", - // "camelcase": "error", - // "capitalized-comments": "error", - // "class-methods-use-this": "error", - // "comma-dangle": "error", - // "comma-spacing": "error", - // "comma-style": "error", - // "complexity": "error", - // "computed-property-spacing": "error", - // "consistent-return": "error", - // "consistent-this": "error", - // "curly": "error", - // "default-case": "error", - // "default-case-last": "error", - // "default-param-last": "error", - // "dot-location": "error", - // "dot-notation": "error", - // "eol-last": "error", - // "eqeqeq": "error", - // "func-call-spacing": "error", - // "func-name-matching": "error", - // "func-names": "error", - // "func-style": "error", - // "function-call-argument-newline": "error", - // "function-paren-newline": "error", - // "generator-star-spacing": "error", - // "global-require": "error", - // "grouped-accessor-pairs": "error", - // "guard-for-in": "error", - // "handle-callback-err": "error", - // "id-blacklist": "error", - // "id-length": "error", - // "id-match": "error", - // "implicit-arrow-linebreak": "error", - // "indent": "error", - // "indent-legacy": "error", - // "init-declarations": "error", - // "jsx-quotes": "error", - // "key-spacing": "error", - // "keyword-spacing": "error", - // "line-comment-position": "error", - // "linebreak-style": "error", - // "lines-around-comment": "error", - // "lines-around-directive": "error", - // "lines-between-class-members": "error", - // "max-classes-per-file": "error", - // "max-depth": "error", - // "max-len": "error", - // "max-lines": "error", - // "max-lines-per-function": "error", - // "max-nested-callbacks": "error", - // "max-params": "error", - // "max-statements": "error", - // "max-statements-per-line": "error", - // "multiline-comment-style": "error", - // "multiline-ternary": "error", - // "new-cap": "error", - // "new-parens": "error", - // "newline-after-var": "error", - // "newline-before-return": "error", - // "newline-per-chained-call": "error", - // "no-alert": "error", - // "no-array-constructor": "error", - // "no-await-in-loop": "error", - // "no-bitwise": "error", - // "no-buffer-constructor": "error", - // "no-caller": "error", - // "no-catch-shadow": "error", - // "no-confusing-arrow": "error", - // "no-console": "error", - // "no-constructor-return": "error", - // "no-continue": "error", - // "no-div-regex": "error", - // "no-duplicate-imports": "error", - // "no-else-return": "error", - // "no-empty-function": "error", - // "no-eq-null": "error", - // "no-eval": "error", - // "no-extend-native": "error", - // "no-extra-bind": "error", - // "no-extra-label": "error", - // "no-extra-parens": "error", - // "no-floating-decimal": "error", - // "no-implicit-coercion": "error", - // "no-implicit-globals": "error", - // "no-implied-eval": "error", - // "no-inline-comments": "error", - // "no-invalid-this": "error", - // "no-iterator": "error", - // "no-label-var": "error", - // "no-labels": "error", - // "no-lone-blocks": "error", - // "no-lonely-if": "error", - // "no-loop-func": "error", - // "no-loss-of-precision": "error", - // "no-magic-numbers": "error", - // "no-mixed-operators": "error", - // "no-mixed-requires": "error", - // "no-multi-assign": "error", - // "no-multi-spaces": "error", - // "no-multi-str": "error", - // "no-multiple-empty-lines": "error", - // "no-native-reassign": "error", - // "no-negated-condition": "error", - // "no-negated-in-lhs": "error", - // "no-nested-ternary": "error", - // "no-new": "error", - // "no-new-func": "error", - // "no-new-object": "error", - // "no-new-require": "error", - // "no-new-wrappers": "error", - // "no-octal-escape": "error", - // "no-param-reassign": "error", - // "no-path-concat": "error", - // "no-plusplus": "error", - // "no-process-env": "error", - // "no-process-exit": "error", - // "no-promise-executor-return": "error", - // "no-proto": "error", - // "no-restricted-exports": "error", - // "no-restricted-globals": "error", - // "no-restricted-imports": "error", - // "no-restricted-modules": "error", - // "no-restricted-properties": "error", - // "no-restricted-syntax": "error", - // "no-return-assign": "error", - // "no-return-await": "error", - // "no-script-url": "error", - // "no-self-compare": "error", - // "no-sequences": "error", - // "no-shadow": "error", - // "no-spaced-func": "error", - // "no-sync": "error", - // "no-tabs": "error", - // "no-template-curly-in-string": "error", - // "no-ternary": "error", - // "no-throw-literal": "error", - // "no-trailing-spaces": "error", - // "no-undef-init": "error", - // "no-undefined": "error", - // "no-underscore-dangle": "error", - // "no-unmodified-loop-condition": "error", - // "no-unneeded-ternary": "error", - // "no-unreachable-loop": "error", - // "no-unused-expressions": "error", - // "no-use-before-define": "error", - // "no-useless-backreference": "error", - // "no-useless-call": "error", - // "no-useless-computed-key": "error", - // "no-useless-concat": "error", - // "no-useless-constructor": "error", - // "no-useless-rename": "error", - // "no-useless-return": "error", - // "no-var": "error", - // "no-void": "error", - // "no-warning-comments": "error", - // "no-whitespace-before-property": "error", - // "nonblock-statement-body-position": "error", - // "object-curly-newline": "error", - // "object-curly-spacing": "error", - // "object-property-newline": "error", - // "object-shorthand": "error", - // "one-var": "error", - // "one-var-declaration-per-line": "error", - // "operator-assignment": "error", - // "operator-linebreak": "error", - // "padded-blocks": "error", - // "padding-line-between-statements": "error", - // "prefer-arrow-callback": "error", - // "prefer-const": "error", - // "prefer-destructuring": "error", - // "prefer-exponentiation-operator": "error", - // "prefer-named-capture-group": "error", - // "prefer-numeric-literals": "error", - // "prefer-object-spread": "error", - // "prefer-promise-reject-errors": "error", - // "prefer-reflect": "error", - // "prefer-regex-literals": "error", - // "prefer-rest-params": "error", - // "prefer-spread": "error", - // "prefer-template": "error", - // "quote-props": "error", - // "quotes": "error", - // "radix": "error", - // "require-atomic-updates": "error", - // "require-await": "error", - // "require-jsdoc": "error", - // "require-unicode-regexp": "error", - // "rest-spread-spacing": "error", - // "semi": "error", - // "semi-spacing": "error", - // "semi-style": "error", - // "sort-imports": "error", - // "sort-keys": "error", - // "sort-vars": "error", - // "space-before-blocks": "error", - // "space-before-function-paren": "error", - // "space-in-parens": "error", - // "space-infix-ops": "error", - // "space-unary-ops": "error", - // "spaced-comment": "error", - // "strict": "error", - // "switch-colon-spacing": "error", - // "symbol-description": "error", - // "template-curly-spacing": "error", - // "template-tag-spacing": "error", - // "unicode-bom": "error", - // "valid-jsdoc": "error", - // "vars-on-top": "error", - // "wrap-iife": "error", - // "wrap-regex": "error", - // "yield-star-spacing": "error", - // "yoda": "error" - - }, - - "settings": { - "react": { - "version": "999.999.999" - } - } - -} diff --git a/.vscode/settings.json b/.vscode/settings.json index 8767a661f..969766466 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,19 @@ +/* +👋 Hi! This file was autogenerated by tslint-to-eslint-config. +https://github.com/typescript-eslint/tslint-to-eslint-config + +It represents the closest reasonable ESLint configuration to this +project's original TSLint configuration. + +We recommend eventually switching this configuration to extend from +the recommended rulesets in typescript-eslint. +https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md + +Happy linting! 💖 +*/ { - "editor.rulers": [120], - "typescript.tsdk": "node_modules/typescript/lib" + "editor.rulers": [ + 120 + ], + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/package.json b/package.json index e5b6fdb62..c37a6e782 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "eslint": "^7.2.0", "eslint-config-airbnb-base": "^14.2.0", "eslint-plugin-import": "^2.21.2", + "eslint-plugin-jsdoc": "^28.5.1", "eslint-plugin-prettier": "^3.1.4", "mocha": "^6.1.4", "nyc": "^14.0.0", diff --git a/src/App.spec.ts b/src/App.spec.ts index cdf960258..c10819651 100644 --- a/src/App.spec.ts +++ b/src/App.spec.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-implicit-dependencies +// eslint-disable import/no-extraneous-dependencies import 'mocha'; import sinon, { SinonSpy } from 'sinon'; import { assert } from 'chai'; @@ -44,7 +44,7 @@ describe('App', () => { withNoopAppMetadata(), withSuccessfulBotUserFetchingWebClient(fakeBotId, fakeBotUserId), ); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ token: '', signingSecret: '' }); @@ -57,7 +57,7 @@ describe('App', () => { it('should succeed with an authorize callback', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ authorize: authorizeCallback, signingSecret: '' }); @@ -69,11 +69,11 @@ describe('App', () => { it('should fail without a token for single team authorization or authorize callback or oauth installer', async () => { // Arrange - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act try { - new App({ signingSecret: '' }); // tslint:disable-line:no-unused-expression + new App({ signingSecret: '' }); // eslint-disable-line @typescript-eslint/no-unused-expressions assert.fail(); } catch (error) { // Assert @@ -83,11 +83,11 @@ describe('App', () => { it('should fail when both a token and authorize callback are specified', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act try { - // tslint:disable-next-line:no-unused-expression + // eslint-disable-line @typescript-eslint/no-unused-expressions new App({ token: '', authorize: authorizeCallback, signingSecret: '' }); assert.fail(); } catch (error) { @@ -99,11 +99,11 @@ describe('App', () => { it('should fail when both a token is specified and OAuthInstaller is initialized', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act try { - // tslint:disable-next-line:no-unused-expression + // eslint-disable-line @typescript-eslint/no-unused-expressions new App({ token: '', clientId: '', clientSecret: '', stateSecret: '', signingSecret: '' }); assert.fail(); } catch (error) { @@ -115,11 +115,11 @@ describe('App', () => { it('should fail when both a authorize callback is specified and OAuthInstaller is initialized', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act try { - // tslint:disable-next-line:no-unused-expression + // eslint-disable-line @typescript-eslint/no-unused-expressions new App({ authorize: authorizeCallback, clientId: '', clientSecret: '', stateSecret: '', signingSecret: '' }); assert.fail(); } catch (error) { @@ -131,7 +131,7 @@ describe('App', () => { describe('with a custom receiver', () => { it('should succeed with no signing secret', async () => { // Arrange - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: new FakeReceiver(), authorize: noopAuthorize }); @@ -142,11 +142,11 @@ describe('App', () => { }); it('should fail when no signing secret for the default receiver is specified', async () => { // Arrange - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act try { - new App({ authorize: noopAuthorize }); // tslint:disable-line:no-unused-expression + new App({ authorize: noopAuthorize }); // eslint-disable-line @typescript-eslint/no-unused-expressions assert.fail(); } catch (error) { // Assert @@ -163,7 +163,7 @@ describe('App', () => { withMemoryStore(fakeMemoryStore), withConversationContext(fakeConversationContext), ); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ authorize: noopAuthorize, signingSecret: '' }); @@ -181,7 +181,7 @@ describe('App', () => { withNoopWebClient(), withConversationContext(fakeConversationContext), ); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ convoStore: false, authorize: noopAuthorize, signingSecret: '' }); @@ -200,7 +200,7 @@ describe('App', () => { withConversationContext(fakeConversationContext), ); const dummyConvoStore = Symbol() as unknown as ConversationStore; - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ convoStore: dummyConvoStore, authorize: noopAuthorize, signingSecret: '' }); @@ -224,11 +224,11 @@ describe('App', () => { }, }, ); - // tslint:disable-next-line: variable-name + // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const App = await importApp(overrides); const clientOptions = { slackApiUrl: 'proxy.slack.com' }; - // tslint:disable-next-line: no-unused-expression + // eslint-disable-line @typescript-eslint/no-unused-expressions new App({ clientOptions, authorize: noopAuthorize, signingSecret: '', logLevel: LogLevel.ERROR }); assert.ok(fakeConstructor.called); @@ -250,7 +250,7 @@ describe('App', () => { const dummyReturn = Symbol(); const dummyParams = [Symbol(), Symbol()]; const fakeReceiver = new FakeReceiver(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const app = new App({ receiver: fakeReceiver, authorize: noopAuthorize }); fakeReceiver.start = sinon.fake.returns(dummyReturn); @@ -269,7 +269,7 @@ describe('App', () => { const dummyReturn = Symbol(); const dummyParams = [Symbol(), Symbol()]; const fakeReceiver = new FakeReceiver(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match fakeReceiver.stop = sinon.fake.returns(dummyReturn); // Act @@ -309,7 +309,7 @@ describe('App', () => { const fakeLogger = createFakeLogger(); const fakeMiddleware = sinon.fake(noopMiddleware); const invalidReceiverEvents = createInvalidReceiverEvents(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: fakeReceiver, logger: fakeLogger, authorize: noopAuthorize }); @@ -329,7 +329,7 @@ describe('App', () => { const dummyOrigError = new Error('auth failed'); const dummyAuthorizationError = new AuthorizationError('auth failed', dummyOrigError); const dummyReceiverEvent = createDummyReceiverEvent(); - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ @@ -363,7 +363,7 @@ describe('App', () => { withMemoryStore(sinon.fake()), withConversationContext(fakeConversationContext), ); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match dummyReceiverEvent = createDummyReceiverEvent(); fakeFirstMiddleware = sinon.fake(noopMiddleware); @@ -512,7 +512,7 @@ describe('App', () => { const dummyReceiverEvent = createDummyReceiverEvent(eventType); beforeEach(async () => { - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match app = new App({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult), @@ -733,7 +733,7 @@ describe('App', () => { const viewFn = sinon.fake.resolves({}); const optionsFn = sinon.fake.resolves({}); const overrides = buildOverrides([withNoopWebClient()]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const dummyReceiverEvents = createReceiverEvents(); // Act @@ -812,7 +812,7 @@ describe('App', () => { const actionId = 'block_action_id'; const fakeAxiosPost = sinon.fake.resolves({}); const overrides = buildOverrides([withNoopWebClient(), withAxiosPost(fakeAxiosPost)]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); @@ -848,7 +848,7 @@ describe('App', () => { const actionId = 'block_action_id'; const fakeAxiosPost = sinon.fake.resolves({}); const overrides = buildOverrides([withNoopWebClient(), withAxiosPost(fakeAxiosPost)]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); @@ -881,7 +881,7 @@ describe('App', () => { it('should be available in middleware/listener args', async () => { // Arrange - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const fakeLogger = createFakeLogger(); const app = new App({ logger: fakeLogger, @@ -928,7 +928,7 @@ describe('App', () => { it('should work in the case both logger and logLevel are given', async () => { // Arrange - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const fakeLogger = createFakeLogger(); const app = new App({ logger: fakeLogger, @@ -980,7 +980,7 @@ describe('App', () => { it('should be available in middleware/listener args', async () => { // Arrange - const App = await importApp(mergeOverrides( // tslint:disable-line:variable-name + const App = await importApp(mergeOverrides( // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match withNoopAppMetadata(), withSuccessfulBotUserFetchingWebClient('B123', 'U123'), )); @@ -1048,7 +1048,7 @@ describe('App', () => { it('should be to the global app client when authorization doesn\'t produce a token', async () => { // Arrange - const App = await importApp(); // tslint:disable-line:variable-name + const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const app = new App({ receiver: fakeReceiver, authorize: noopAuthorize, @@ -1140,7 +1140,7 @@ describe('App', () => { // Arrange const fakePostMessage = sinon.fake.resolves({}); const overrides = buildOverrides([withPostMessage(fakePostMessage)]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const dummyMessage = 'test'; const dummyReceiverEvents = createChannelContextualReceiverEvents(dummyChannelId); @@ -1170,7 +1170,7 @@ describe('App', () => { // Arrange const fakePostMessage = sinon.fake.resolves({}); const overrides = buildOverrides([withPostMessage(fakePostMessage)]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const dummyMessage = { text: 'test' }; const dummyReceiverEvents = createChannelContextualReceiverEvents(dummyChannelId); @@ -1247,7 +1247,7 @@ describe('App', () => { it('should not exist in the arguments on incoming events that don\'t support say', async () => { // Arrange const overrides = buildOverrides([withNoopWebClient()]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const assertionAggregator = sinon.fake(); const dummyReceiverEvents = createReceiverEventsWithoutSay(dummyChannelId); @@ -1271,7 +1271,7 @@ describe('App', () => { // Arrange const fakePostMessage = sinon.fake.rejects(new Error('fake error')); const overrides = buildOverrides([withPostMessage(fakePostMessage)]); - const App = await importApp(overrides); // tslint:disable-line:variable-name + const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match const dummyMessage = { text: 'test' }; const dummyReceiverEvents = createChannelContextualReceiverEvents(dummyChannelId); diff --git a/tslint-to-eslint-config.log b/tslint-to-eslint-config.log new file mode 100644 index 000000000..094791164 --- /dev/null +++ b/tslint-to-eslint-config.log @@ -0,0 +1,55 @@ +5 ESLint rules behave differently from their TSLint counterparts: + * camelcase: + - Leading undescores in variable names will now be ignored. + * no-underscore-dangle: + - Leading and trailing underscores (_) on identifiers will now be ignored. + * eqeqeq: + - Option "smart" allows for comparing two literal values, evaluating the value of typeof and null comparisons. + * @typescript-eslint/quotes: + - Option "jsx-double" is not supported by ESLint. + - Option "avoid-template" is not supported by ESLint. + * @typescript-eslint/no-unused-expressions: + - The TSLint optional config "allow-new" is the default ESLint behavior and will no longer be ignored. + +Could not resolve ESLint extension 'eslint-plugin-airbnb-base'.: Error: Cannot find module '/Users/kattari/Desktop/bolt-js/eslint-plugin-airbnb-base' +Require stack: +- /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/adapters/nativeImporter.js +- /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/cli/main.js +- /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/bin/tslint-to-eslint-config + at Function.Module._resolveFilename (internal/modules/cjs/loader.js:794:15) + at Function.Module._load (internal/modules/cjs/loader.js:687:27) + at Module.require (internal/modules/cjs/loader.js:849:19) + at require (internal/modules/cjs/helpers.js:74:18) + at /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/adapters/nativeImporter.js:25:64 + at processTicksAndRejections (internal/process/task_queues.js:93:5) + at async Object.exports.nativeImporter (/Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/adapters/nativeImporter.js:25:16) + at async importFile (/Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/input/importer.js:32:20) + at async exports.importer (/Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/input/importer.js:50:28) + at async /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/creation/summarization/retrieveExtendsValues.js:50:26 + + +20 rules are not known by tslint-to-eslint-config to have ESLint equivalents: + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "array-bracket-spacing". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "block-spacing". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "brace-style". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "function-name". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "import-name". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-dynamic-delete". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-else-after-return". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-function-constructor-with-string-args". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-increment-decrement". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "object-curly-spacing". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "object-shorthand-properties-first". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "prefer-array-literal". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "space-in-parens". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-arrow-parens". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-computed-property-spacing". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-func-call-spacing". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-indent". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-prefer-arrow-callback". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "typedef". + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "whitespace". + +1 editor setting is not known by tslint-to-eslint-config to have an ESLint equivalent: + * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "editor.rulers". + From 380b5fbf9d2847e7276e4f7e188b79a0df0f7d4e Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Fri, 26 Jun 2020 12:16:58 -0400 Subject: [PATCH 06/19] updated some rules --- .eslintignore | 5 +++ .eslintrc.js | 91 ++++++++++++++++++++++++++------------------------- 2 files changed, 51 insertions(+), 45 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..6e0889aa0 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +# spec files +**/*.spec.ts + +# test helpers +src/test-helpers.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 3eb4db1b4..36674d2e6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,7 @@ Happy linting! 💖 module.exports = { "env": { "es6": true, - "node": true + "node": true, }, "extends": [ "airbnb-base" @@ -142,7 +142,7 @@ module.exports = { "1tbs" ], "callback-return": "off", - "camelcase": "error", + "camelcase": "off", "capitalized-comments": [ "off", "never", @@ -389,7 +389,7 @@ module.exports = { ], "import/no-webpack-loader-syntax": "error", "import/order": [ - "error", + "off", { "groups": [ [ @@ -400,51 +400,52 @@ module.exports = { ] } ], - "import/prefer-default-export": "error", + "import/prefer-default-export": "off", "import/unambiguous": "off", "indent": [ - "error", + "off", 2, - { - "SwitchCase": 1, - "VariableDeclarator": 1, - "outerIIFEBody": 1, - "FunctionDeclaration": { - "parameters": 1, - "body": 1 - }, - "FunctionExpression": { - "parameters": 1, - "body": 1 - }, - "CallExpression": { - "arguments": 1 - }, - "ArrayExpression": 1, - "ObjectExpression": 1, - "ImportDeclaration": 1, - "flatTernaryExpressions": false, - "ignoredNodes": [ - "JSXElement", - "JSXElement > *", - "JSXAttribute", - "JSXIdentifier", - "JSXNamespacedName", - "JSXMemberExpression", - "JSXSpreadAttribute", - "JSXExpressionContainer", - "JSXOpeningElement", - "JSXClosingElement", - "JSXFragment", - "JSXOpeningFragment", - "JSXClosingFragment", - "JSXText", - "JSXEmptyExpression", - "JSXSpreadChild" - ], - "ignoreComments": false, - "offsetTernaryExpressions": false - } + // optional additions if ESLint indent is used + // { + // "SwitchCase": 1, + // "VariableDeclarator": 1, + // "outerIIFEBody": 1, + // "FunctionDeclaration": { + // "parameters": 1, + // "body": 1 + // }, + // "FunctionExpression": { + // "parameters": 1, + // "body": 1 + // }, + // "CallExpression": { + // "arguments": 1 + // }, + // "ArrayExpression": 1, + // "ObjectExpression": 1, + // "ImportDeclaration": 1, + // "flatTernaryExpressions": false, + // "ignoredNodes": [ + // "JSXElement", + // "JSXElement > *", + // "JSXAttribute", + // "JSXIdentifier", + // "JSXNamespacedName", + // "JSXMemberExpression", + // "JSXSpreadAttribute", + // "JSXExpressionContainer", + // "JSXOpeningElement", + // "JSXClosingElement", + // "JSXFragment", + // "JSXOpeningFragment", + // "JSXClosingFragment", + // "JSXText", + // "JSXEmptyExpression", + // "JSXSpreadChild" + // ], + // "ignoreComments": false, + // "offsetTernaryExpressions": false + // } ], "init-declarations": "off", // "jsdoc/check-alignment": "error", From d63bae2e5de5923bf2652ea4ffca2686a28f3949 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Fri, 26 Jun 2020 13:13:07 -0400 Subject: [PATCH 07/19] created test file --- .eslintignore | 5 ----- .eslintrc.js | 3 ++- .eslintrc.test.js | 13 +++++++++++++ package.json | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) delete mode 100644 .eslintignore create mode 100644 .eslintrc.test.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 6e0889aa0..000000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -# spec files -**/*.spec.ts - -# test helpers -src/test-helpers.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 36674d2e6..fff880ce8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1409,5 +1409,6 @@ module.exports = { "node_modules", "\\.(coffee|scss|css|less|hbs|svg|json)$" ] - } + }, + "ignorePatterns": ["**/*.spec.ts", "src/test-helpers.ts"], }; diff --git a/.eslintrc.test.js b/.eslintrc.test.js new file mode 100644 index 000000000..914403f42 --- /dev/null +++ b/.eslintrc.test.js @@ -0,0 +1,13 @@ +module.exports = { + // FIXME + "extends": [".eslintrc.js"], + "overrides": [{ + "files": ["**/*.spec.ts", "src/test-helpers.ts"], + "env":{ + "mocha": true + }, + "parserOptions": { + "project": "tsconfig.test.json" + } + }] +}; diff --git a/package.json b/package.json index c37a6e782..ff496edc1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", "lint": "eslint src/**/*.ts", - "test-lint": "tslint --project tsconfig.test.json \"src/**/*.spec.ts\" && tslint --project tsconfig.test.json \"src/test-helpers.ts\"", + "test-lint": "eslint -c .eslintrc.test.js \"src/**/*.spec.ts\" --no-ignore && eslint -c .eslintrc.test.js \"src/test-helpers.ts\" --no-ignore", "mocha": "TS_NODE_PROJECT=tsconfig.test.json nyc mocha --config .mocharc.json \"src/**/*.spec.ts\"", "test": "npm run lint && npm run test-lint && npm run mocha && npm run test:types", "test:types": "tsd", From 6db1d94e65ee7f49e54dc673b4cdd9df7ab43db5 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Fri, 26 Jun 2020 13:44:49 -0400 Subject: [PATCH 08/19] leading underscore rule added --- .eslintrc.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fff880ce8..9b22b879f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,7 +17,9 @@ module.exports = { "node": true, }, "extends": [ - "airbnb-base" + "airbnb-base", + // "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "parser": "@typescript-eslint/parser", "parserOptions": { @@ -30,7 +32,20 @@ module.exports = { ], "rules": { "@typescript-eslint/await-thenable": "error", - "@typescript-eslint/class-name-casing": "error", + "@typescript-eslint/naming-convention": [ + "error", + // custom rule to ignore cases that require quoting + { + "selector": "property", + "format": ["camelCase"], + "leadingUnderscore": "allow", + "filter": { + // you can expand this regex as you find more cases that require quoting that you want to allow + "regex": "[_ ]", + "match": false + } + } + ], "@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/consistent-type-definitions": "error", "@typescript-eslint/explicit-member-accessibility": [ @@ -72,12 +87,12 @@ module.exports = { "@typescript-eslint/no-empty-function": "error", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-for-in-array": "error", - "@typescript-eslint/no-param-reassign": "error", "@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-this-alias": "error", "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", "@typescript-eslint/no-unused-expressions": "error", "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/prefer-regexp-exec" : "off", "@typescript-eslint/quotes": [ "error", "single", From 29334055136d8dadf867bfa60a11409f31afb11e Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Fri, 26 Jun 2020 17:25:20 -0400 Subject: [PATCH 09/19] added test lint + cleaned up --- .eslintignore | 6 ++ .eslintrc.js | 176 ++++-------------------------------- .eslintrc.test.js | 27 ++++-- package.json | 5 +- tsconfig.eslint.json | 11 +++ tslint-to-eslint-config.log | 55 ----------- 6 files changed, 55 insertions(+), 225 deletions(-) create mode 100644 .eslintignore create mode 100644 tsconfig.eslint.json delete mode 100644 tslint-to-eslint-config.log diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..3fa632304 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +# builtin file has a lot of formatting issues +builtin.ts + +**/*.spec.ts + +src/test-helpers.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 9b22b879f..d0df94d2b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,34 +1,23 @@ -/* -👋 Hi! This file was autogenerated by tslint-to-eslint-config. -https://github.com/typescript-eslint/tslint-to-eslint-config - -It represents the closest reasonable ESLint configuration to this -project's original TSLint configuration. - -We recommend eventually switching this configuration to extend from -the recommended rulesets in typescript-eslint. -https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md - -Happy linting! 💖 -*/ module.exports = { "env": { "es6": true, "node": true, }, "extends": [ - "airbnb-base", + "airbnb-typescript/base", // "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking" + "plugin:@typescript-eslint/recommended-requiring-type-checking", + // "plugin:@typescript-eslint/eslint-recommended" + // 'plugin:import/errors', + // 'plugin:import/warnings', ], "parser": "@typescript-eslint/parser", "parserOptions": { - "project": "tsconfig.json", + "project": "tsconfig.eslint.json", "sourceType": "module" }, "plugins": [ - "@typescript-eslint", - "@typescript-eslint/tslint" + "@typescript-eslint" ], "rules": { "@typescript-eslint/await-thenable": "error", @@ -37,7 +26,7 @@ module.exports = { // custom rule to ignore cases that require quoting { "selector": "property", - "format": ["camelCase"], + "format": ["camelCase", "UPPER_CASE"], "leadingUnderscore": "allow", "filter": { // you can expand this regex as you find more cases that require quoting that you want to allow @@ -48,6 +37,7 @@ module.exports = { ], "@typescript-eslint/consistent-type-assertions": "error", "@typescript-eslint/consistent-type-definitions": "error", + "@typescript-eslint/dot-notation": "warn", "@typescript-eslint/explicit-member-accessibility": [ "error", { @@ -55,7 +45,7 @@ module.exports = { } ], "@typescript-eslint/indent": [ - "error", + "off", 2, { "CallExpression": { @@ -85,6 +75,7 @@ module.exports = { } ], "@typescript-eslint/no-empty-function": "error", + "@typescript-eslint/no-dynamic-delete" : "error", "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-require-imports": "error", @@ -105,6 +96,8 @@ module.exports = { "always" ], "@typescript-eslint/strict-boolean-expressions": "error", + // Equivalent of typedef in TSLint + "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/type-annotation-spacing": "error", "accessor-pairs": "off", "array-bracket-newline": [ @@ -237,13 +230,6 @@ module.exports = { "error", "property" ], - "dot-notation": [ - "error", - { - "allowKeywords": true, - "allowPattern": "" - } - ], "eol-last": "error", "eqeqeq": [ "error", @@ -381,13 +367,6 @@ module.exports = { "import/no-self-import": "error", "import/no-unassigned-import": "off", "import/no-unresolved": "off", - // "import/no-unresolved": [ - // "error", - // { - // "commonjs": true, - // "caseSensitive": true - // } - // ], "import/no-unused-modules": [ "off", { @@ -417,51 +396,7 @@ module.exports = { ], "import/prefer-default-export": "off", "import/unambiguous": "off", - "indent": [ - "off", - 2, - // optional additions if ESLint indent is used - // { - // "SwitchCase": 1, - // "VariableDeclarator": 1, - // "outerIIFEBody": 1, - // "FunctionDeclaration": { - // "parameters": 1, - // "body": 1 - // }, - // "FunctionExpression": { - // "parameters": 1, - // "body": 1 - // }, - // "CallExpression": { - // "arguments": 1 - // }, - // "ArrayExpression": 1, - // "ObjectExpression": 1, - // "ImportDeclaration": 1, - // "flatTernaryExpressions": false, - // "ignoredNodes": [ - // "JSXElement", - // "JSXElement > *", - // "JSXAttribute", - // "JSXIdentifier", - // "JSXNamespacedName", - // "JSXMemberExpression", - // "JSXSpreadAttribute", - // "JSXExpressionContainer", - // "JSXOpeningElement", - // "JSXClosingElement", - // "JSXFragment", - // "JSXOpeningFragment", - // "JSXClosingFragment", - // "JSXText", - // "JSXEmptyExpression", - // "JSXSpreadChild" - // ], - // "ignoreComments": false, - // "offsetTernaryExpressions": false - // } - ], + "indent": ["off", 2], "init-declarations": "off", // "jsdoc/check-alignment": "error", // "jsdoc/check-indentation": "error", @@ -1321,85 +1256,7 @@ module.exports = { "error", "after" ], - "yoda": "error", - "@typescript-eslint/tslint/config": [ - "error", - { - "rules": { - "array-bracket-spacing": [ - true, - "never" - ], - "block-spacing": true, - "brace-style": [ - true, - "1tbs", - { - "allowSingleLine": true - } - ], - "function-name": [ - true, - { - "function-regex": {}, - "method-regex": {}, - "private-method-regex": {}, - "protected-method-regex": {}, - "static-method-regex": {} - } - ], - "import-name": true, - "no-dynamic-delete": true, - "no-else-after-return": true, - "no-function-constructor-with-string-args": true, - "no-increment-decrement": true, - "object-curly-spacing": [ - true, - "always" - ], - "object-shorthand-properties-first": true, - "prefer-array-literal": true, - "space-in-parens": [ - true, - "never" - ], - "ter-arrow-parens": [ - true, - "as-needed", - { - "requireForBlockBody": true - } - ], - "ter-computed-property-spacing": true, - "ter-func-call-spacing": true, - "ter-indent": [ - true, - 2, - { - "SwitchCase": 1 - } - ], - "ter-prefer-arrow-callback": true, - "typedef": [ - true, - "call-signature" - ], - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-preblock", - "check-type", - "check-module", - "check-separator", - "check-rest-spread", - "check-typecast", - "check-type-operator" - ] - } - } - ] + "yoda": "error" }, "settings": { "react": { @@ -1424,6 +1281,5 @@ module.exports = { "node_modules", "\\.(coffee|scss|css|less|hbs|svg|json)$" ] - }, - "ignorePatterns": ["**/*.spec.ts", "src/test-helpers.ts"], + } }; diff --git a/.eslintrc.test.js b/.eslintrc.test.js index 914403f42..c7d1f4b4c 100644 --- a/.eslintrc.test.js +++ b/.eslintrc.test.js @@ -1,13 +1,26 @@ module.exports = { - // FIXME - "extends": [".eslintrc.js"], - "overrides": [{ - "files": ["**/*.spec.ts", "src/test-helpers.ts"], - "env":{ + "extends": [ + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + "env": { + "commonjs": true, "mocha": true }, + "parser": "@typescript-eslint/parser", "parserOptions": { - "project": "tsconfig.test.json" + "project": "tsconfig.test.json", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "@typescript-eslint/no-unnecessary-type-assertion": "warn", + "prefer-rest-params": "off" } - }] + }; diff --git a/package.json b/package.json index ff496edc1..c60acd3fd 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "build": "tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", "lint": "eslint src/**/*.ts", - "test-lint": "eslint -c .eslintrc.test.js \"src/**/*.spec.ts\" --no-ignore && eslint -c .eslintrc.test.js \"src/test-helpers.ts\" --no-ignore", + "test-lint": "eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" --no-ignore && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\" --no-ignore", "mocha": "TS_NODE_PROJECT=tsconfig.test.json nyc mocha --config .mocharc.json \"src/**/*.spec.ts\"", "test": "npm run lint && npm run test-lint && npm run mocha && npm run test:types", "test:types": "tsd", @@ -65,10 +65,9 @@ "chai": "^4.2.0", "codecov": "^3.2.0", "eslint": "^7.2.0", - "eslint-config-airbnb-base": "^14.2.0", + "eslint-config-airbnb-typescript": "^8.0.2", "eslint-plugin-import": "^2.21.2", "eslint-plugin-jsdoc": "^28.5.1", - "eslint-plugin-prettier": "^3.1.4", "mocha": "^6.1.4", "nyc": "^14.0.0", "rewiremock": "^3.13.4", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 000000000..a109abc36 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,11 @@ +{ + // extend your base config to share compilerOptions, etc + "extends": "./tsconfig.json", + "compilerOptions": { + // ensure that this config cannot be used for a build + "noEmit": true + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file diff --git a/tslint-to-eslint-config.log b/tslint-to-eslint-config.log deleted file mode 100644 index 094791164..000000000 --- a/tslint-to-eslint-config.log +++ /dev/null @@ -1,55 +0,0 @@ -5 ESLint rules behave differently from their TSLint counterparts: - * camelcase: - - Leading undescores in variable names will now be ignored. - * no-underscore-dangle: - - Leading and trailing underscores (_) on identifiers will now be ignored. - * eqeqeq: - - Option "smart" allows for comparing two literal values, evaluating the value of typeof and null comparisons. - * @typescript-eslint/quotes: - - Option "jsx-double" is not supported by ESLint. - - Option "avoid-template" is not supported by ESLint. - * @typescript-eslint/no-unused-expressions: - - The TSLint optional config "allow-new" is the default ESLint behavior and will no longer be ignored. - -Could not resolve ESLint extension 'eslint-plugin-airbnb-base'.: Error: Cannot find module '/Users/kattari/Desktop/bolt-js/eslint-plugin-airbnb-base' -Require stack: -- /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/adapters/nativeImporter.js -- /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/cli/main.js -- /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/bin/tslint-to-eslint-config - at Function.Module._resolveFilename (internal/modules/cjs/loader.js:794:15) - at Function.Module._load (internal/modules/cjs/loader.js:687:27) - at Module.require (internal/modules/cjs/loader.js:849:19) - at require (internal/modules/cjs/helpers.js:74:18) - at /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/adapters/nativeImporter.js:25:64 - at processTicksAndRejections (internal/process/task_queues.js:93:5) - at async Object.exports.nativeImporter (/Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/adapters/nativeImporter.js:25:16) - at async importFile (/Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/input/importer.js:32:20) - at async exports.importer (/Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/input/importer.js:50:28) - at async /Users/kattari/.npm/_npx/2695/lib/node_modules/tslint-to-eslint-config/src/creation/summarization/retrieveExtendsValues.js:50:26 - - -20 rules are not known by tslint-to-eslint-config to have ESLint equivalents: - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "array-bracket-spacing". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "block-spacing". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "brace-style". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "function-name". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "import-name". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-dynamic-delete". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-else-after-return". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-function-constructor-with-string-args". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "no-increment-decrement". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "object-curly-spacing". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "object-shorthand-properties-first". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "prefer-array-literal". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "space-in-parens". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-arrow-parens". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-computed-property-spacing". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-func-call-spacing". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-indent". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "ter-prefer-arrow-callback". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "typedef". - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "whitespace". - -1 editor setting is not known by tslint-to-eslint-config to have an ESLint equivalent: - * tslint-to-eslint-config does not know the ESLint equivalent for TSLint's "editor.rulers". - From 43c23b4a448bfb59e8532256e3443d27553afb2b Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Fri, 26 Jun 2020 17:44:45 -0400 Subject: [PATCH 10/19] some more cleanup in the config --- .eslintrc.js | 7 +------ tsconfig.eslint.json | 5 +---- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d0df94d2b..e68809a48 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -971,12 +971,7 @@ module.exports = { // } // ], "no-use-before-define": [ - "error", - { - "functions": true, - "classes": true, - "variables": true - } + "off" ], "no-useless-backreference": "off", "no-useless-call": "off", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index a109abc36..c225026ad 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -4,8 +4,5 @@ "compilerOptions": { // ensure that this config cannot be used for a build "noEmit": true - }, - "include": [ - "src/**/*.ts" - ] + } } \ No newline at end of file From e8bc8922562e22111419eb0fe8fc53346a9ad7d9 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Mon, 6 Jul 2020 11:30:42 -0400 Subject: [PATCH 11/19] reverted ts upgrade and specified eslintignore --- .eslintignore | 2 +- package.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 3fa632304..d079d1063 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,6 @@ # builtin file has a lot of formatting issues builtin.ts -**/*.spec.ts +src/**/*.spec.ts src/test-helpers.ts \ No newline at end of file diff --git a/package.json b/package.json index c60acd3fd..88b4242a4 100644 --- a/package.json +++ b/package.json @@ -77,10 +77,14 @@ "ts-node": "^8.1.0", "tsd": "^0.13.1", "tslint": "^5.15.0", +<<<<<<< HEAD "tslint-config-airbnb": "^5.11.1", "typescript": "^3.7.2" }, "tsd": { "directory": "types-tests" +======= + "typescript": "^3.7.2" +>>>>>>> reverted ts upgrade and specified eslintignore } } From d65b6946a3a3de0d08e3c9b4ef8eb0dea81d8ca2 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 8 Jul 2020 11:22:50 -0400 Subject: [PATCH 12/19] added prettier, cleaned + optimized eslintrc files --- .eslintignore | 7 +- .eslintrc.js | 1339 ++------------------------------ .eslintrc.test.js | 48 +- .prettierrc.json | 15 + eslint-config-base/index.js | 71 ++ package.json | 12 +- src/App.spec.ts | 67 +- src/App.ts | 5 +- src/ExpressReceiver.ts | 2 + src/conversation-store.spec.ts | 10 +- src/errors.ts | 1 + src/index.ts | 2 +- src/middleware/builtin.ts | 4 +- tsconfig.eslint.json | 2 +- tsconfig.test.json | 5 +- 15 files changed, 231 insertions(+), 1359 deletions(-) create mode 100644 .prettierrc.json create mode 100644 eslint-config-base/index.js diff --git a/.eslintignore b/.eslintignore index d079d1063..b512c09d4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1 @@ -# builtin file has a lot of formatting issues -builtin.ts - -src/**/*.spec.ts - -src/test-helpers.ts \ No newline at end of file +node_modules \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index e68809a48..8c31ae633 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,1280 +1,63 @@ module.exports = { - "env": { - "es6": true, - "node": true, - }, - "extends": [ - "airbnb-typescript/base", - // "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - // "plugin:@typescript-eslint/eslint-recommended" - // 'plugin:import/errors', - // 'plugin:import/warnings', - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.eslint.json", - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/await-thenable": "error", - "@typescript-eslint/naming-convention": [ - "error", - // custom rule to ignore cases that require quoting - { - "selector": "property", - "format": ["camelCase", "UPPER_CASE"], - "leadingUnderscore": "allow", - "filter": { - // you can expand this regex as you find more cases that require quoting that you want to allow - "regex": "[_ ]", - "match": false - } - } - ], - "@typescript-eslint/consistent-type-assertions": "error", - "@typescript-eslint/consistent-type-definitions": "error", - "@typescript-eslint/dot-notation": "warn", - "@typescript-eslint/explicit-member-accessibility": [ - "error", - { - "accessibility": "explicit" - } - ], - "@typescript-eslint/indent": [ - "off", - 2, - { - "CallExpression": { - "arguments": "first" - }, - "ArrayExpression": "first", - "ObjectExpression": "first", - "FunctionDeclaration": { - "parameters": "first" - }, - "FunctionExpression": { - "parameters": "first" - } - } - ], - "@typescript-eslint/member-delimiter-style": [ - "error", - { - "multiline": { - "delimiter": "semi", - "requireLast": true - }, - "singleline": { - "delimiter": "semi", - "requireLast": false - } - } - ], - "@typescript-eslint/no-empty-function": "error", - "@typescript-eslint/no-dynamic-delete" : "error", - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-for-in-array": "error", - "@typescript-eslint/no-require-imports": "error", - "@typescript-eslint/no-this-alias": "error", - "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", - "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/no-var-requires": "error", - "@typescript-eslint/prefer-regexp-exec" : "off", - "@typescript-eslint/quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "@typescript-eslint/semi": [ - "error", - "always" - ], - "@typescript-eslint/strict-boolean-expressions": "error", - // Equivalent of typedef in TSLint - "@typescript-eslint/explicit-function-return-type": "error", - "@typescript-eslint/type-annotation-spacing": "error", - "accessor-pairs": "off", - "array-bracket-newline": [ - "off", - "consistent" - ], - "array-bracket-spacing": [ - "error", - "never" - ], - "array-callback-return": [ - "error", - { - "allowImplicit": true, - "checkForEach": false - } - ], - "array-element-newline": [ - "off", - { - "multiline": true, - "minItems": 3 - } - ], - "arrow-body-style": [ - "error", - "as-needed", - { - "requireReturnForObjectLiteral": false - } - ], - "arrow-parens": [ - "off", - "always" - ], - "arrow-spacing": [ - "error", - { - "before": true, - "after": true - } - ], - "block-scoped-var": "error", - "block-spacing": [ - "error", - "always" - ], - "brace-style": [ - "error", - "1tbs" - ], - "callback-return": "off", - "camelcase": "off", - "capitalized-comments": [ - "off", - "never", - { - "line": { - "ignorePattern": ".*", - "ignoreInlineComments": true, - "ignoreConsecutiveComments": true - }, - "block": { - "ignorePattern": ".*", - "ignoreInlineComments": true, - "ignoreConsecutiveComments": true - } - } - ], - "class-methods-use-this": [ - "error", - { - "exceptMethods": [] - } - ], - "comma-dangle": [ - "error", - "always-multiline" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "comma-style": [ - "error", - "last", - { - "exceptions": { - "ArrayExpression": false, - "ArrayPattern": false, - "ArrowFunctionExpression": false, - "CallExpression": false, - "FunctionDeclaration": false, - "FunctionExpression": false, - "ImportDeclaration": false, - "ObjectExpression": false, - "ObjectPattern": false, - "VariableDeclaration": false, - "NewExpression": false - } - } - ], - "complexity": [ - "off", - 11 - ], - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-return": "error", - "consistent-this": "off", - "constructor-super": "error", - "curly": [ - "error", - "multi-line" - ], - "default-case": [ - "error", - { - "commentPattern": "^no default$" - } - ], - "default-case-last": "off", - "default-param-last": "off", - "dot-location": [ - "error", - "property" - ], - "eol-last": "error", - "eqeqeq": [ - "error", - "smart" - ], - "for-direction": "error", - "func-call-spacing": [ - "error", - "never" - ], - "func-name-matching": [ - "off", - "always", - { - "includeCommonJSModuleExports": false, - "considerPropertyDescriptor": true - } - ], - "func-names": "warn", - "func-style": [ - "off", - "expression" - ], - "function-call-argument-newline": [ - "off", - "consistent" - ], - "function-paren-newline": [ - "error", - "consistent" - ], - "generator-star-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "getter-return": [ - "error", - { - "allowImplicit": true - } - ], - "global-require": "error", - "grouped-accessor-pairs": "off", - "guard-for-in": "error", - "handle-callback-err": "off", - "id-blacklist": [ - "error", - "any", - "Number", - "number", - "String", - "string", - "Boolean", - "boolean", - "Undefined", - "undefined" - ], - "id-length": "off", - "id-match": "error", - "implicit-arrow-linebreak": [ - "error", - "beside" - ], - "import/default": "off", - "import/dynamic-import-chunkname": [ - "off", - { - "importFunctions": [], - "webpackChunknameFormat": "[0-9a-zA-Z-_/.]+" - } - ], - "import/export": "error", - "import/exports-last": "off", - "import/extensions": "off", - // "import/extensions": [ - // "error", - // "ignorePackages", - // { - // "js": "never", - // // "mjs": "never", - // "ts": "never" - // } - // ], - "import/first": "error", - "import/group-exports": "off", - "import/imports-first": "off", - "import/max-dependencies": [ - "off", - { - "max": 10 - } - ], - "import/named": "error", - "import/namespace": "off", - "import/newline-after-import": "error", - "import/no-absolute-path": "error", - "import/no-amd": "error", - "import/no-anonymous-default-export": [ - "off", - { - "allowArray": false, - "allowArrowFunction": false, - "allowAnonymousClass": false, - "allowAnonymousFunction": false, - "allowLiteral": false, - "allowObject": false - } - ], - "import/no-commonjs": "off", - // "import/no-cycle": [ - // "error", - // { - // "maxDepth": null, - // "ignoreExternal": false - // } - // ], - "import/no-default-export": "off", - "import/no-deprecated": "off", - "import/no-duplicates": "error", - "import/no-dynamic-require": "error", - "import/no-extraneous-dependencies": "error", - "import/no-internal-modules": "error", - "import/no-mutable-exports": "error", - "import/no-named-as-default": "error", - "import/no-named-as-default-member": "error", - "import/no-named-default": "error", - "import/no-named-export": "off", - "import/no-namespace": "off", - "import/no-nodejs-modules": "off", - "import/no-relative-parent-imports": "off", - "import/no-restricted-paths": "off", - "import/no-self-import": "error", - "import/no-unassigned-import": "off", - "import/no-unresolved": "off", - "import/no-unused-modules": [ - "off", - { - "ignoreExports": [], - "missingExports": true, - "unusedExports": true - } - ], - "import/no-useless-path-segments": [ - "error", - { - "commonjs": true - } - ], - "import/no-webpack-loader-syntax": "error", - "import/order": [ - "off", - { - "groups": [ - [ - "builtin", - "external", - "internal" - ] - ] - } - ], - "import/prefer-default-export": "off", - "import/unambiguous": "off", - "indent": ["off", 2], - "init-declarations": "off", - // "jsdoc/check-alignment": "error", - // "jsdoc/check-indentation": "error", - // "jsdoc/newline-after-description": "error", - // "jsdoc/no-types": "error", - // "jsx-quotes": [ - // "off", - // "prefer-double" - // ], - "key-spacing": [ - "error", - { - "beforeColon": false, - "afterColon": true - } - ], - "keyword-spacing": [ - "error", - { - "before": true, - "after": true, - "overrides": { - "return": { - "after": true - }, - "throw": { - "after": true - }, - "case": { - "after": true - } - } - } - ], - "line-comment-position": [ - "off", - { - "position": "above", - "ignorePattern": "", - "applyDefaultPatterns": true - } - ], - "linebreak-style": [ - "error", - "unix" - ], - "lines-around-comment": "off", - "lines-around-directive": [ - "error", - { - "before": "always", - "after": "always" - } - ], - "lines-between-class-members": [ - "error", - "always", - { - "exceptAfterSingleLine": false - } - ], - "max-classes-per-file": [ - "error", - 1 - ], - "max-depth": [ - "off", - 4 - ], - "max-len": [ - "error", - { - "code": 120 - } - ], - "max-lines": [ - "off", - { - "max": 300, - "skipBlankLines": true, - "skipComments": true - } - ], - "max-lines-per-function": [ - "off", - { - "max": 50, - "skipBlankLines": true, - "skipComments": true, - "IIFEs": true - } - ], - "max-nested-callbacks": "off", - "max-params": [ - "off", - 3 - ], - "max-statements": [ - "off", - 10 - ], - "max-statements-per-line": [ - "off", - { - "max": 1 - } - ], - "multiline-comment-style": [ - "off", - "starred-block" - ], - "multiline-ternary": [ - "off", - "never" - ], - "new-cap": [ - "error", - { - "newIsCap": true, - "newIsCapExceptions": [], - "capIsNew": false, - "capIsNewExceptions": [ - "Immutable.Map", - "Immutable.Set", - "Immutable.List" - ], - "properties": true - } - ], - "new-parens": "error", - "newline-after-var": "off", - "newline-before-return": "off", - "newline-per-chained-call": [ - "error", - { - "ignoreChainWithDepth": 4 - } - ], - "no-alert": "warn", - "no-array-constructor": "error", - "no-async-promise-executor": "error", - "no-await-in-loop": "error", - "no-bitwise": "error", - "no-buffer-constructor": "error", - "no-caller": "error", - "no-case-declarations": "error", - "no-catch-shadow": "off", - "no-class-assign": "error", - "no-compare-neg-zero": "error", - "no-cond-assign": [ - "error", - "always" - ], - "no-confusing-arrow": [ - "error", - { - "allowParens": true - } - ], - "no-console": "warn", - "no-const-assign": "error", - "no-constant-condition": "warn", - "no-constructor-return": "off", - "no-continue": "error", - "no-control-regex": "error", - "no-debugger": "error", - "no-delete-var": "error", - "no-div-regex": "off", - "no-dupe-args": "error", - "no-dupe-class-members": "error", - "no-dupe-else-if": "off", - "no-dupe-keys": "error", - "no-duplicate-case": "error", - "no-duplicate-imports": "error", - "no-else-return": [ - "error", - { - "allowElseIf": false - } - ], - "no-empty": "error", - "no-empty-character-class": "error", - "no-empty-function": [ - "error", - { - "allow": [ - "arrowFunctions", - "functions", - "methods" - ] - } - ], - "no-empty-pattern": "error", - "no-eq-null": "off", - "no-eval": "error", - "no-ex-assign": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-boolean-cast": "error", - "no-extra-label": "error", - "no-extra-parens": [ - "off", - "all", - { - "conditionalAssign": true, - "nestedBinaryExpressions": false, - "returnAssign": false, - "ignoreJSX": "all", - "enforceForArrowConditionals": false - } - ], - "no-extra-semi": "error", - "no-fallthrough": "error", - "no-floating-decimal": "error", - "no-func-assign": "error", - "no-global-assign": [ - "error", - { - "exceptions": [] - } - ], - "no-implicit-coercion": [ - "off", - { - "boolean": false, - "number": true, - "string": true, - "allow": [] - } - ], - "no-implicit-globals": "off", - "no-implied-eval": "error", - "no-import-assign": "off", - "no-inline-comments": "off", - "no-inner-declarations": "error", - "no-invalid-regexp": "error", - "no-invalid-this": "off", - "no-irregular-whitespace": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": [ - "error", - { - "allowLoop": false, - "allowSwitch": false - } - ], - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-loss-of-precision": "off", - "no-magic-numbers": [ - "off", - { - "ignore": [], - "ignoreArrayIndexes": true, - "enforceConst": true, - "detectObjects": false - } - ], - "no-misleading-character-class": "error", - "no-mixed-operators": [ - "error", - { - "groups": [ - [ - "%", - "**" - ], - [ - "%", - "+" - ], - [ - "%", - "-" - ], - [ - "%", - "*" - ], - [ - "%", - "/" - ], - [ - "/", - "*" - ], - [ - "&", - "|", - "<<", - ">>", - ">>>" - ], - [ - "==", - "!=", - "===", - "!==" - ], - [ - "&&", - "||" - ] - ], - "allowSamePrecedence": false - } - ], - "no-mixed-requires": [ - "off", - false - ], - "no-mixed-spaces-and-tabs": "error", - "no-multi-assign": "error", - "no-multi-spaces": [ - "error", - { - "ignoreEOLComments": false - } - ], - "no-multi-str": "error", - "no-multiple-empty-lines": "error", - "no-native-reassign": "off", - "no-negated-condition": "off", - "no-negated-in-lhs": "off", - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-symbol": "error", - "no-new-wrappers": "error", - "no-obj-calls": "error", - "no-octal": "error", - "no-octal-escape": "error", - "no-param-reassign": [ - "error", - { - "props": true, - "ignorePropertyModificationsFor": [ - "acc", - "accumulator", - "e", - "ctx", - "context", - "req", - "request", - "res", - "response", - "$scope", - "staticContext" - ] - } - ], - "no-path-concat": "error", - "no-plusplus": "error", - "no-process-env": "off", - "no-process-exit": "off", - "no-proto": "error", - "no-prototype-builtins": "error", - "no-redeclare": "error", - "no-regex-spaces": "error", - "no-restricted-exports": [ - "off", - { - "restrictedNamedExports": [ - "default", - "then" - ] - } - ], - "no-restricted-globals": [ - "error", - "isFinite", - "isNaN", - "addEventListener", - "blur", - "close", - "closed", - "confirm", - "defaultStatus", - "defaultstatus", - "event", - "external", - "find", - "focus", - "frameElement", - "frames", - "history", - "innerHeight", - "innerWidth", - "length", - "location", - "locationbar", - "menubar", - "moveBy", - "moveTo", - "name", - "onblur", - "onerror", - "onfocus", - "onload", - "onresize", - "onunload", - "open", - "opener", - "opera", - "outerHeight", - "outerWidth", - "pageXOffset", - "pageYOffset", - "parent", - "print", - "removeEventListener", - "resizeBy", - "resizeTo", - "screen", - "screenLeft", - "screenTop", - "screenX", - "screenY", - "scroll", - "scrollbars", - "scrollBy", - "scrollTo", - "scrollX", - "scrollY", - "self", - "status", - "statusbar", - "stop", - "toolbar", - "top" - ], - "no-restricted-imports": [ - "off", - { - "paths": [], - "patterns": [] - } - ], - "no-restricted-modules": "off", - "no-restricted-properties": [ - "error", - { - "object": "arguments", - "property": "callee", - "message": "arguments.callee is deprecated" - }, - { - "object": "global", - "property": "isFinite", - "message": "Please use Number.isFinite instead" - }, - { - "object": "self", - "property": "isFinite", - "message": "Please use Number.isFinite instead" - }, - { - "object": "window", - "property": "isFinite", - "message": "Please use Number.isFinite instead" - }, - { - "object": "global", - "property": "isNaN", - "message": "Please use Number.isNaN instead" - }, - { - "object": "self", - "property": "isNaN", - "message": "Please use Number.isNaN instead" - }, - { - "object": "window", - "property": "isNaN", - "message": "Please use Number.isNaN instead" - }, - { - "property": "__defineGetter__", - "message": "Please use Object.defineProperty instead." - }, - { - "property": "__defineSetter__", - "message": "Please use Object.defineProperty instead." - }, - { - "object": "Math", - "property": "pow", - "message": "Use the exponentiation operator (**) instead." - } - ], - "no-restricted-syntax": [ - "error", - { - "selector": "ForInStatement", - "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." - }, - { - "selector": "ForOfStatement", - "message": "iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations." - }, - { - "selector": "LabeledStatement", - "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." - }, - { - "selector": "WithStatement", - "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." - } - ], - "no-return-assign": [ - "error", - "always" - ], - "no-return-await": "error", - "no-script-url": "error", - "no-self-assign": [ - "error", - { - "props": true - } - ], - "no-self-compare": "error", - "no-sequences": "error", - "no-setter-return": "off", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-spaced-func": "error", - "no-sparse-arrays": "error", - "no-sync": "off", - "no-tabs": "error", - "no-template-curly-in-string": "error", - "no-ternary": "off", - "no-this-before-super": "error", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-undef": "error", - "no-undef-init": "error", - "no-undefined": "off", - "no-underscore-dangle": "error", - "no-unexpected-multiline": "error", - "no-unmodified-loop-condition": "off", - "no-unneeded-ternary": [ - "error", - { - "defaultAssignment": false - } - ], - "no-unreachable": "error", - "no-unsafe-finally": "error", - "no-unsafe-negation": "error", - "no-unused-expressions": [ - "error", - { - "allowShortCircuit": false, - "allowTernary": false, - "allowTaggedTemplates": false - } - ], - "no-unused-labels": "error", - "no-unused-vars": "off", - // "no-unused-vars": [ - // "error", - // { - // "vars": "all", - // "args": "after-used", - // "ignoreRestSiblings": true - // } - // ], - "no-use-before-define": [ - "off" - ], - "no-useless-backreference": "off", - "no-useless-call": "off", - "no-useless-catch": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-escape": "error", - "no-useless-rename": [ - "error", - { - "ignoreDestructuring": false, - "ignoreImport": false, - "ignoreExport": false - } - ], - "no-useless-return": "error", - "no-var": "error", - "no-void": "error", - "no-warning-comments": [ - "off", - { - "terms": [ - "todo", - "fixme", - "xxx" - ], - "location": "start" - } - ], - "no-whitespace-before-property": "error", - "no-with": "error", - "nonblock-statement-body-position": [ - "error", - "beside", - { - "overrides": {} - } - ], - "object-curly-newline": [ - "error", - { - "ObjectExpression": { - "minProperties": 4, - "multiline": true, - "consistent": true - }, - "ObjectPattern": { - "minProperties": 4, - "multiline": true, - "consistent": true - }, - "ImportDeclaration": { - "minProperties": 4, - "multiline": true, - "consistent": true - }, - "ExportDeclaration": { - "minProperties": 4, - "multiline": true, - "consistent": true - } - } - ], - "object-curly-spacing": [ - "error", - "always" - ], - "object-property-newline": [ - "error", - { - "allowAllPropertiesOnSameLine": true, - "allowMultiplePropertiesPerLine": false - } - ], - "object-shorthand": "error", - "one-var": [ - "error", - "never" - ], - "one-var-declaration-per-line": [ - "error", - "always" - ], - "operator-assignment": [ - "error", - "always" - ], - "operator-linebreak": [ - "error", - "before", - { - "overrides": { - "=": "none" - } - } - ], - "padded-blocks": [ - "error", - { - "blocks": "never", - "classes": "never", - "switches": "never" - }, - { - "allowSingleLineBlocks": true - } - ], - "padding-line-between-statements": "off", - "prefer-arrow-callback": [ - "error", - { - "allowNamedFunctions": false, - "allowUnboundThis": true - } - ], - "prefer-const": "error", - "prefer-destructuring": [ - "error", - { - "VariableDeclarator": { - "array": false, - "object": true - }, - "AssignmentExpression": { - "array": true, - "object": false - } - }, - { - "enforceForRenamedProperties": false - } - ], - "prefer-exponentiation-operator": "off", - "prefer-named-capture-group": "off", - "prefer-numeric-literals": "error", - "prefer-object-spread": "error", - "prefer-promise-reject-errors": [ - "error", - { - "allowEmptyReject": true - } - ], - "prefer-reflect": "off", - "prefer-regex-literals": "off", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - "quote-props": [ - "error", - "as-needed" - ], - "quotes": [ - "error", - "single", - { - "avoidEscape": true - } - ], - "radix": "error", - "require-atomic-updates": "off", - "require-await": "off", - "require-jsdoc": "off", - "require-unicode-regexp": "off", - "require-yield": "error", - "rest-spread-spacing": [ - "error", - "never" - ], - "semi": [ - "error", - "always" - ], - "semi-spacing": [ - "error", - { - "before": false, - "after": true - } - ], - "semi-style": [ - "error", - "last" - ], - "sort-imports": [ - "off", - { - "ignoreCase": false, - "ignoreDeclarationSort": false, - "ignoreMemberSort": false, - "memberSyntaxSortOrder": [ - "none", - "all", - "multiple", - "single" - ] - } - ], - "sort-keys": [ - "off", - "asc", - { - "caseSensitive": false, - "natural": true - } - ], - "sort-vars": "off", - "space-before-blocks": "error", - "space-before-function-paren": [ - "error", - { - "anonymous": "always", - "named": "never" - } - ], - "space-in-parens": [ - "error", - "never" - ], - "space-infix-ops": "error", - "space-unary-ops": [ - "error", - { - "words": true, - "nonwords": false, - "overrides": {} - } - ], - "spaced-comment": [ - "error", - "always", - { - "markers": [ - "/" - ] - } - ], - "strict": [ - "error", - "never" - ], - "switch-colon-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "symbol-description": "error", - "template-curly-spacing": "error", - "template-tag-spacing": [ - "error", - "never" - ], - "unicode-bom": [ - "error", - "never" - ], - "use-isnan": "error", - "valid-jsdoc": "off", - "valid-typeof": [ - "error", - { - "requireStringLiterals": true - } - ], - "vars-on-top": "error", - "wrap-iife": [ - "error", - "outside", - { - "functionPrototypeMethods": false - } - ], - "wrap-regex": "off", - "yield-star-spacing": [ - "error", - "after" - ], - "yoda": "error" - }, - "settings": { - "react": { - "version": "999.999.999" - }, - "import/resolver": { - "node": { - "extensions": [ - ".mjs", - ".js", - ".json" - ] - } - }, - "import/extensions": [ - ".js", - // ".mjs", - ".ts" - ], - "import/core-modules": [], - "import/ignore": [ - "node_modules", - "\\.(coffee|scss|css|less|hbs|svg|json)$" - ] - } + env: { + es6: true, + node: true, + }, + extends: [ + 'airbnb-typescript/base', + /* TODO: Uncomment rule below once jsdoc comments are added. + This matches the jsdoc rules in the TSLint config */ + // "plugin:jsdoc/recommended", + 'prettier', + 'prettier/@typescript-eslint', + './eslint-config-base', // the common settings in eslint-config-base + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.eslint.json', + }, + plugins: ['@typescript-eslint', 'jsdoc'], + ignorePatterns: ["**/*.spec.ts", "src/test-helpers.ts"], + rules: { + /* Below are some of the new 'airbnb-typescript' rules that the project currently does not follow. + They've been disabled here since they raise errors in a few files. The best course + of action is likely to adopt these rules and make the quick (and mostly automated) fixes + needed in the repo to conform to these. ESLint and the airbnb-typecript config is more strict + than the original TSLint configuration that this project had. */ + 'import/first': ['off'], + 'import/prefer-default-export': ['off'], + 'import/newline-after-import': ['off'], + 'import/no-cycle': ['off'], + 'import/no-useless-path-segments': ['off'], + 'import/order': ['off'], + 'max-classes-per-file': ['off', 1], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/lines-between-class-members': 'off', + 'no-nested-ternary': 'off', + 'no-restricted-globals': 'off', + 'no-lonely-if': 'off', + 'no-undef-init': 'off', + 'no-multi-assign': 'off', + 'prefer-object-spread': 'off', + 'consistent-return': 'off', + 'no-restricted-syntax': 'off', + 'prefer-destructuring': 'off', + + /* Some currently-enabled additional rules. Uncomment to disable. The project currently conforms to them + so there it's best to just keep these commented or delete them entirely */ + // '@typescript-eslint/ban-types': 'off', + // '@typescript-eslint/no-empty-interface': 'off', + // '@typescript-eslint/no-unsafe-assign': 'off', + // '@typescript-eslint/no-explicit-any': 'off', + // '@typescript-eslint/no-unsafe-member-access': 'off', + // '@typescript-eslint/no-unsafe-return': 'off', + // '@typescript-eslint/no-unnecessary-type-assertion': 'off', + // '@typescript-eslint/no-non-null-assertion': 'off', + // '@typescript-eslint/no-unsafe-assignment': 'off', + // '@typescript-eslint/no-unsafe-call': 'off', + // '@typescript-eslint/restrict-template-expressions': 'off', + // '@typescript-eslint/unbound-method': 'off', + // '@typescript-eslint/explicit-module-boundary-types': 'off', + // '@typescript-eslint/require-await': 'off', + }, }; diff --git a/.eslintrc.test.js b/.eslintrc.test.js index c7d1f4b4c..947ee288e 100644 --- a/.eslintrc.test.js +++ b/.eslintrc.test.js @@ -1,26 +1,26 @@ module.exports = { - "extends": [ - "plugin:@typescript-eslint/recommended-requiring-type-checking" - ], - "env": { - "commonjs": true, - "mocha": true - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "tsconfig.test.json", - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/require-await": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "@typescript-eslint/no-unnecessary-type-assertion": "warn", - "prefer-rest-params": "off" - } - + extends: [ + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + './eslint-config-base', + ], + env: { + commonjs: true, + mocha: true, + }, + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.test.json', + }, + plugins: ['@typescript-eslint'], + rules: { + /* The rules below currently raise errors. They are easy and mostly automated fixes so conforming to them + would be a good idea. Make sure to remove the rules below if you chose to adopt these rules. */ + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'warn', + 'prefer-rest-params': 'off', + 'brace-style': 'off', + }, }; diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..358ac0dd6 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,15 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "useTabs": true, + "tabWidth": 4, + "printWidth": 120, + "overrides": [ + { + "files": "*.ts", + "options": { + "parser": "typescript" + } + } + ] +} \ No newline at end of file diff --git a/eslint-config-base/index.js b/eslint-config-base/index.js new file mode 100644 index 000000000..558335b3e --- /dev/null +++ b/eslint-config-base/index.js @@ -0,0 +1,71 @@ +module.exports = { + rules: { + // The rules below are ESLint equivalents for the old TSLint rules in tslint.json + // Matches the quotemark rule + '@typescript-eslint/quotes': [ + 'error', + 'single', + { + 'avoidEscape': true, + 'allowTemplateLiterals': false, + }, + ], + // matches the variable-name rule + '@typescript-eslint/naming-convention': [ + 'error', + // custom rule to ignore cases that require quoting + { + 'selector': 'variableLike', + 'format': ['camelCase', 'UPPER_CASE'], + 'leadingUnderscore': 'allow', + 'filter': { + // you can expand this regex as you find more cases that require quoting that you want to allow + 'regex': '[_ ]', + 'match': false, + }, + }, + ], + // matches ban-comma-operator rule + 'no-sequences': 'error', + // matches the await-promise rule + '@typescript-eslint/await-thenable': 'error', + // matches interface-over-type-literal rule + '@typescript-eslint/consistent-type-definitions': 'error', + // matches member-access rule + '@typescript-eslint/explicit-member-accessibility': [ + 'error', + { + 'accessibility': 'explicit', + 'overrides': { + 'constructors': 'no-public', + }, + }, + ], + // matches no-duplicate-switch-case + 'no-duplicate-case': 'error', + // matches no-duplicate-variable + 'no-redeclare': 'error', + // matches no-require-imports + '@typescript-eslint/no-require-imports': 'error', + // matches no-return-await + 'no-return-await': 'error', + // matches no-submodule-imports (slightly different) + // "import/no-internal-modules": "error", + // matches no-this-assignment + '@typescript-eslint/no-this-alias': 'error', + // matches no-unused-expression + '@typescript-eslint/no-unused-expressions': 'error', + // matches no-var-requires + '@typescript-eslint/no-var-requires': 'error', + // sorta matches one-line (Prettier takes care of this) + 'brace-style': ['error', '1tbs'], + // matches strict-boolean-expressions + '@typescript-eslint/strict-boolean-expressions': 'error', + // matches typedef + // REVIEW: This raised errors in a couple of the files. To view these errors, its value from 'off' to 'error' + '@typescript-eslint/explicit-function-return-type': 'off', + // matches typedef-whitespace + '@typescript-eslint/type-annotation-spacing': 'error', + // max-line-length is matched by Prettier + }, +}; diff --git a/package.json b/package.json index 88b4242a4..3ef5dfa42 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "prepare": "npm run build", "build": "tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", - "lint": "eslint src/**/*.ts", - "test-lint": "eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" --no-ignore && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\" --no-ignore", + "lint": "eslint --ext .ts src", + "test-lint": "eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\"", "mocha": "TS_NODE_PROJECT=tsconfig.test.json nyc mocha --config .mocharc.json \"src/**/*.spec.ts\"", "test": "npm run lint && npm run test-lint && npm run mocha && npm run test:types", "test:types": "tsd", @@ -51,6 +51,7 @@ "axios": "^0.19.0", "express": "^4.16.4", "please-upgrade-node": "^3.2.0", + "prettier": "^2.0.5", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" @@ -59,14 +60,15 @@ "@types/chai": "^4.1.7", "@types/mocha": "^5.2.6", "@types/sinon": "^7.0.11", - "@typescript-eslint/eslint-plugin": "^3.4.0", + "@typescript-eslint/eslint-plugin": "^3.6.0", "@typescript-eslint/eslint-plugin-tslint": "^3.4.0", "@typescript-eslint/parser": "^3.4.0", "chai": "^4.2.0", "codecov": "^3.2.0", - "eslint": "^7.2.0", + "eslint": "^7.3.1", "eslint-config-airbnb-typescript": "^8.0.2", - "eslint-plugin-import": "^2.21.2", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-import": "^2.22.0", "eslint-plugin-jsdoc": "^28.5.1", "mocha": "^6.1.4", "nyc": "^14.0.0", diff --git a/src/App.spec.ts b/src/App.spec.ts index c10819651..1b841e80f 100644 --- a/src/App.spec.ts +++ b/src/App.spec.ts @@ -1,4 +1,3 @@ -// eslint-disable import/no-extraneous-dependencies import 'mocha'; import sinon, { SinonSpy } from 'sinon'; import { assert } from 'chai'; @@ -44,7 +43,7 @@ describe('App', () => { withNoopAppMetadata(), withSuccessfulBotUserFetchingWebClient(fakeBotId, fakeBotUserId), ); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ token: '', signingSecret: '' }); @@ -57,7 +56,7 @@ describe('App', () => { it('should succeed with an authorize callback', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ authorize: authorizeCallback, signingSecret: '' }); @@ -69,7 +68,7 @@ describe('App', () => { it('should fail without a token for single team authorization or authorize callback or oauth installer', async () => { // Arrange - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act try { @@ -83,7 +82,7 @@ describe('App', () => { it('should fail when both a token and authorize callback are specified', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act try { @@ -99,7 +98,7 @@ describe('App', () => { it('should fail when both a token is specified and OAuthInstaller is initialized', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act try { @@ -115,7 +114,7 @@ describe('App', () => { it('should fail when both a authorize callback is specified and OAuthInstaller is initialized', async () => { // Arrange const authorizeCallback = sinon.fake(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act try { @@ -131,7 +130,7 @@ describe('App', () => { describe('with a custom receiver', () => { it('should succeed with no signing secret', async () => { // Arrange - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: new FakeReceiver(), authorize: noopAuthorize }); @@ -142,7 +141,7 @@ describe('App', () => { }); it('should fail when no signing secret for the default receiver is specified', async () => { // Arrange - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act try { @@ -163,7 +162,7 @@ describe('App', () => { withMemoryStore(fakeMemoryStore), withConversationContext(fakeConversationContext), ); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ authorize: noopAuthorize, signingSecret: '' }); @@ -181,7 +180,7 @@ describe('App', () => { withNoopWebClient(), withConversationContext(fakeConversationContext), ); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ convoStore: false, authorize: noopAuthorize, signingSecret: '' }); @@ -200,7 +199,7 @@ describe('App', () => { withConversationContext(fakeConversationContext), ); const dummyConvoStore = Symbol() as unknown as ConversationStore; - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ convoStore: dummyConvoStore, authorize: noopAuthorize, signingSecret: '' }); @@ -224,12 +223,12 @@ describe('App', () => { }, }, ); - // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match - const App = await importApp(overrides); + + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const clientOptions = { slackApiUrl: 'proxy.slack.com' }; - // eslint-disable-line @typescript-eslint/no-unused-expressions - new App({ clientOptions, authorize: noopAuthorize, signingSecret: '', logLevel: LogLevel.ERROR }); + + new App({ clientOptions, authorize: noopAuthorize, signingSecret: '', logLevel: LogLevel.ERROR }); // eslint-disable-line @typescript-eslint/no-unused-expressions assert.ok(fakeConstructor.called); @@ -250,7 +249,7 @@ describe('App', () => { const dummyReturn = Symbol(); const dummyParams = [Symbol(), Symbol()]; const fakeReceiver = new FakeReceiver(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const app = new App({ receiver: fakeReceiver, authorize: noopAuthorize }); fakeReceiver.start = sinon.fake.returns(dummyReturn); @@ -269,11 +268,11 @@ describe('App', () => { const dummyReturn = Symbol(); const dummyParams = [Symbol(), Symbol()]; const fakeReceiver = new FakeReceiver(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match fakeReceiver.stop = sinon.fake.returns(dummyReturn); // Act - const app = new App({ receiver: fakeReceiver, authorize: noopAuthorize }); + const app = new App({ receiver: fakeReceiver, authorize: noopAuthorize }); // eslint-disable-line @typescript-eslint/no-unused-expressions const actualReturn = await app.stop(...dummyParams); // Assert @@ -309,7 +308,7 @@ describe('App', () => { const fakeLogger = createFakeLogger(); const fakeMiddleware = sinon.fake(noopMiddleware); const invalidReceiverEvents = createInvalidReceiverEvents(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: fakeReceiver, logger: fakeLogger, authorize: noopAuthorize }); @@ -329,7 +328,7 @@ describe('App', () => { const dummyOrigError = new Error('auth failed'); const dummyAuthorizationError = new AuthorizationError('auth failed', dummyOrigError); const dummyReceiverEvent = createDummyReceiverEvent(); - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ @@ -363,7 +362,7 @@ describe('App', () => { withMemoryStore(sinon.fake()), withConversationContext(fakeConversationContext), ); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match dummyReceiverEvent = createDummyReceiverEvent(); fakeFirstMiddleware = sinon.fake(noopMiddleware); @@ -512,7 +511,7 @@ describe('App', () => { const dummyReceiverEvent = createDummyReceiverEvent(eventType); beforeEach(async () => { - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match app = new App({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult), @@ -733,7 +732,7 @@ describe('App', () => { const viewFn = sinon.fake.resolves({}); const optionsFn = sinon.fake.resolves({}); const overrides = buildOverrides([withNoopWebClient()]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const dummyReceiverEvents = createReceiverEvents(); // Act @@ -812,7 +811,7 @@ describe('App', () => { const actionId = 'block_action_id'; const fakeAxiosPost = sinon.fake.resolves({}); const overrides = buildOverrides([withNoopWebClient(), withAxiosPost(fakeAxiosPost)]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); @@ -848,7 +847,7 @@ describe('App', () => { const actionId = 'block_action_id'; const fakeAxiosPost = sinon.fake.resolves({}); const overrides = buildOverrides([withNoopWebClient(), withAxiosPost(fakeAxiosPost)]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act const app = new App({ receiver: fakeReceiver, authorize: sinon.fake.resolves(dummyAuthorizationResult) }); @@ -881,7 +880,7 @@ describe('App', () => { it('should be available in middleware/listener args', async () => { // Arrange - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const fakeLogger = createFakeLogger(); const app = new App({ logger: fakeLogger, @@ -928,7 +927,7 @@ describe('App', () => { it('should work in the case both logger and logLevel are given', async () => { // Arrange - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const fakeLogger = createFakeLogger(); const app = new App({ logger: fakeLogger, @@ -980,7 +979,7 @@ describe('App', () => { it('should be available in middleware/listener args', async () => { // Arrange - const App = await importApp(mergeOverrides( // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(mergeOverrides( // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match withNoopAppMetadata(), withSuccessfulBotUserFetchingWebClient('B123', 'U123'), )); @@ -1048,7 +1047,7 @@ describe('App', () => { it('should be to the global app client when authorization doesn\'t produce a token', async () => { // Arrange - const App = await importApp(); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const app = new App({ receiver: fakeReceiver, authorize: noopAuthorize, @@ -1140,7 +1139,7 @@ describe('App', () => { // Arrange const fakePostMessage = sinon.fake.resolves({}); const overrides = buildOverrides([withPostMessage(fakePostMessage)]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const dummyMessage = 'test'; const dummyReceiverEvents = createChannelContextualReceiverEvents(dummyChannelId); @@ -1170,7 +1169,7 @@ describe('App', () => { // Arrange const fakePostMessage = sinon.fake.resolves({}); const overrides = buildOverrides([withPostMessage(fakePostMessage)]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const dummyMessage = { text: 'test' }; const dummyReceiverEvents = createChannelContextualReceiverEvents(dummyChannelId); @@ -1247,7 +1246,7 @@ describe('App', () => { it('should not exist in the arguments on incoming events that don\'t support say', async () => { // Arrange const overrides = buildOverrides([withNoopWebClient()]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const assertionAggregator = sinon.fake(); const dummyReceiverEvents = createReceiverEventsWithoutSay(dummyChannelId); @@ -1271,7 +1270,7 @@ describe('App', () => { // Arrange const fakePostMessage = sinon.fake.rejects(new Error('fake error')); const overrides = buildOverrides([withPostMessage(fakePostMessage)]); - const App = await importApp(overrides); // eslint-disable-line camelcase, no-underscore-dangle, id-blacklist, id-match + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const dummyMessage = { text: 'test' }; const dummyReceiverEvents = createChannelContextualReceiverEvents(dummyChannelId); diff --git a/src/App.ts b/src/App.ts index 126bb0d7f..ed58f11e2 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-member-accessibility, @typescript-eslint/strict-boolean-expressions */ import { Agent } from 'http'; import { SecureContextOptions } from 'tls'; import util from 'util'; @@ -55,8 +56,8 @@ import { AppInitializationError, MultipleListenerError, } from './errors'; -import allSettled = require('promise.allsettled'); // tslint:disable-line:no-require-imports import-name -const packageJson = require('../package.json'); // tslint:disable-line:no-require-imports no-var-requires +import allSettled = require('promise.allsettled'); // eslint-disable-line @typescript-eslint/no-require-imports +const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires /** App initialization options */ export interface AppOptions { diff --git a/src/ExpressReceiver.ts b/src/ExpressReceiver.ts index c7df980f1..8d88d8cc8 100644 --- a/src/ExpressReceiver.ts +++ b/src/ExpressReceiver.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-member-accessibility, @typescript-eslint/strict-boolean-expressions */ + import { AnyMiddlewareArgs, Receiver, ReceiverEvent } from './types'; import { createServer, Server } from 'http'; import express, { Request, Response, Application, RequestHandler, Router } from 'express'; diff --git a/src/conversation-store.spec.ts b/src/conversation-store.spec.ts index 61f82851f..ba1ed3894 100644 --- a/src/conversation-store.spec.ts +++ b/src/conversation-store.spec.ts @@ -1,4 +1,4 @@ -// tslint:disable:no-implicit-dependencies +// eslint-disable import/no-extraneous-dependencies import 'mocha'; import { assert, AssertionError } from 'chai'; import sinon, { SinonSpy } from 'sinon'; @@ -125,7 +125,7 @@ describe('MemoryStore', () => { describe('constructor', () => { it('should initialize successfully', async () => { // Arrange - const { MemoryStore } = await importConversationStore(); + const { MemoryStore } = await importConversationStore(); // eslint-disable-line @typescript-eslint/naming-convention // Act const store = new MemoryStore(); @@ -143,7 +143,7 @@ describe('MemoryStore', () => { // Arrange const dummyConversationState = Symbol(); const dummyConversationId = 'CONVERSATION_ID'; - const { MemoryStore } = await importConversationStore(); + const { MemoryStore } = await importConversationStore(); // eslint-disable-line @typescript-eslint/naming-convention // Act const store = new MemoryStore(); @@ -156,7 +156,7 @@ describe('MemoryStore', () => { it('should reject lookup of conversation state when the conversation is not stored', async () => { // Arrange - const { MemoryStore } = await importConversationStore(); + const { MemoryStore } = await importConversationStore(); // eslint-disable-line @typescript-eslint/naming-convention // Act const store = new MemoryStore(); @@ -175,7 +175,7 @@ describe('MemoryStore', () => { const dummyConversationId = 'CONVERSATION_ID'; const dummyConversationState = Symbol(); const expiresInMs = 5; - const { MemoryStore } = await importConversationStore(); + const { MemoryStore } = await importConversationStore(); // eslint-disable-line @typescript-eslint/naming-convention // Act const store = new MemoryStore(); diff --git a/src/errors.ts b/src/errors.ts index 50f8817f3..b626e06f8 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ export interface CodedError extends Error { code: string; // This can be a value from ErrorCode, or WebClient's ErrorCode, or a NodeJS error code } diff --git a/src/index.ts b/src/index.ts index 84431a28d..f88e42741 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -const packageJson = require('../package.json'); // tslint:disable-line:no-require-imports no-var-requires +const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires import pleaseUpgradeNode from 'please-upgrade-node'; pleaseUpgradeNode(packageJson); diff --git a/src/middleware/builtin.ts b/src/middleware/builtin.ts index 68ca834d2..5f29c4e89 100644 --- a/src/middleware/builtin.ts +++ b/src/middleware/builtin.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/dot-notation */ + import { Middleware, AnyMiddlewareArgs, @@ -311,7 +313,7 @@ export function ignoreSelf(): Middleware { }; } -export function subtype(subtype: string): Middleware> { +export function subtype(subtype: string): Middleware> { // eslint-disable-line no-shadow return async ({ message, next }) => { if (message.subtype === subtype) { // TODO: remove the non-null assertion operator diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index c225026ad..b10b1b2ff 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -4,5 +4,5 @@ "compilerOptions": { // ensure that this config cannot be used for a build "noEmit": true - } + }, } \ No newline at end of file diff --git a/tsconfig.test.json b/tsconfig.test.json index a8d4317b4..67566a4c3 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,4 +1,5 @@ { - "extends": "./tsconfig.json", - "exclude": [] + "extends": "./tsconfig.json", + "include": ["**/*.spec.ts", "src/test-helpers.ts"], + "exclude": [] } From bdfcbe4a3e33a6aa573f44ad08763ca89be4362a Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 8 Jul 2020 11:56:18 -0400 Subject: [PATCH 13/19] removed old package and delete tslint.json --- package.json | 1 - tslint.json | 64 ---------------------------------------------------- 2 files changed, 65 deletions(-) delete mode 100644 tslint.json diff --git a/package.json b/package.json index 3ef5dfa42..8270bc659 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "@types/mocha": "^5.2.6", "@types/sinon": "^7.0.11", "@typescript-eslint/eslint-plugin": "^3.6.0", - "@typescript-eslint/eslint-plugin-tslint": "^3.4.0", "@typescript-eslint/parser": "^3.4.0", "chai": "^4.2.0", "codecov": "^3.2.0", diff --git a/tslint.json b/tslint.json deleted file mode 100644 index e35b345bb..000000000 --- a/tslint.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "extends": ["tslint-config-airbnb"], - "rules": { - // adds statements, members, and elements to the base config - "align": [true, "parameters", "arguments", "statements", "members", "elements"], - // adds number of spaces so auto-fixing will work - "indent": [true, "spaces", 2], - // increase value from 100 in base config to 120 - "max-line-length": [true, 120], - // adds avoid-escape and avoid-template - "quotemark": [true, "single", "avoid-escape", "avoid-template", "jsx-double"], - // adds ban-keywords and allow-leading-underscores - // once this gets implemented, we should incorporate it: https://github.com/palantir/tslint/issues/3442 - "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], - // adds check-module, check-type, check-rest-spread, check-typecast, check-type-operator - "whitespace": [true, - "check-branch", - "check-decl", - "check-operator", - "check-preblock", - "check-type", - "check-module", - "check-separator", - "check-rest-spread", - "check-typecast", - "check-type-operator" - ], - // not used in base config - "await-promise": true, - "ban-comma-operator": true, - // TODO: enable completed-docs after transition from TSLint to ESLint is complete - "completed-docs": false, - "interface-over-type-literal": true, - "jsdoc-format": [true, "check-multiline-start"], - "member-access": [true], - "no-duplicate-switch-case": true, - "no-duplicate-variable": true, - "no-dynamic-delete": true, - "no-empty": true, - "no-floating-promises": true, - "no-for-in-array": true, - "no-implicit-dependencies": true, - "no-object-literal-type-assertion": true, - "no-redundant-jsdoc": true, - "no-require-imports": true, - "no-return-await": true, - "no-submodule-imports": true, - "no-this-assignment": true, - "no-unused-expression": true, - "no-var-requires": true, - "one-line": [true, "check-else", "check-whitespace", "check-open-brace", "check-catch", "check-finally"], - "strict-boolean-expressions": [true, "allow-boolean-or-undefined"], - "typedef": [true, "call-signature"], - "typedef-whitespace": [true, { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - }] - // TODO: find a rule similar to https://palantir.github.io/tslint/rules/no-construct/, except it bans those types - // from interfaces (e.g. a function that returns Boolean is an error, it should return boolean) - } -} From 48a8e3ea1ddc84f2b1b04d222ed86e55b9f6dd8c Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 8 Jul 2020 12:12:02 -0400 Subject: [PATCH 14/19] removed tslint from devDependencies --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index 8270bc659..af4abc066 100644 --- a/package.json +++ b/package.json @@ -77,15 +77,10 @@ "source-map-support": "^0.5.12", "ts-node": "^8.1.0", "tsd": "^0.13.1", - "tslint": "^5.15.0", -<<<<<<< HEAD "tslint-config-airbnb": "^5.11.1", "typescript": "^3.7.2" }, "tsd": { "directory": "types-tests" -======= - "typescript": "^3.7.2" ->>>>>>> reverted ts upgrade and specified eslintignore } } From f0197fd2d7fef96eb5b09c514d8d43c652c6bca5 Mon Sep 17 00:00:00 2001 From: Kian Attari Date: Wed, 8 Jul 2020 12:16:52 -0400 Subject: [PATCH 15/19] jsdoc doesn't need to be a plugin --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8c31ae633..45c83f24c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,7 +16,7 @@ module.exports = { parserOptions: { project: './tsconfig.eslint.json', }, - plugins: ['@typescript-eslint', 'jsdoc'], + plugins: ['@typescript-eslint'], ignorePatterns: ["**/*.spec.ts", "src/test-helpers.ts"], rules: { /* Below are some of the new 'airbnb-typescript' rules that the project currently does not follow. From b82dc4466e70f1e80e0ad8dfb51f060a92758bad Mon Sep 17 00:00:00 2001 From: Steve Gill Date: Thu, 23 Jul 2020 13:33:57 -0700 Subject: [PATCH 16/19] actually ran prettier --- .prettierrc.json | 2 - .vscode/settings.json | 15 +- package.json | 6 +- src/App.spec.ts | 312 +++++++++++++--------- src/App.ts | 316 ++++++++++++----------- src/ExpressReceiver.spec.ts | 88 +++++-- src/ExpressReceiver.ts | 86 +++--- src/conversation-store.spec.ts | 28 +- src/errors.spec.ts | 1 - src/errors.ts | 4 +- src/helpers.ts | 17 +- src/index.ts | 10 +- src/middleware/builtin.spec.ts | 99 ++++--- src/middleware/builtin.ts | 71 +++-- src/middleware/process.ts | 6 +- src/test-helpers.ts | 6 +- src/types/actions/block-action.ts | 8 +- src/types/actions/index.ts | 20 +- src/types/actions/interactive-message.ts | 2 +- src/types/events/base-events.ts | 29 +-- src/types/events/index.ts | 5 +- src/types/helpers.ts | 8 +- src/types/middleware.ts | 11 +- src/types/options/index.ts | 11 +- src/types/utilities.ts | 5 +- src/types/view/index.ts | 12 +- tsconfig.json | 3 +- 27 files changed, 638 insertions(+), 543 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 358ac0dd6..306853acf 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,8 +1,6 @@ { "singleQuote": true, "trailingComma": "all", - "useTabs": true, - "tabWidth": 4, "printWidth": 120, "overrides": [ { diff --git a/.vscode/settings.json b/.vscode/settings.json index 969766466..ae3b4995d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,8 +12,15 @@ https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FA Happy linting! 💖 */ { - "editor.rulers": [ - 120 - ], - "typescript.tsdk": "node_modules/typescript/lib" + "editor.rulers": [120], + "typescript.tsdk": "node_modules/typescript/lib", + "editor.formatOnSave": false, + "[javascript]": { + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.formatOnSave": true + }, + "prettier.configPath": ".prettierrc.json", + "editor.tabSize": 2, } diff --git a/package.json b/package.json index af4abc066..ac929d711 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "prepare": "npm run build", "build": "tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", - "lint": "eslint --ext .ts src", - "test-lint": "eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\"", + "lint": "prettier 'src/**/*.ts' --write && eslint --ext .ts src", + "test-lint": "eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" --fix && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\"", "mocha": "TS_NODE_PROJECT=tsconfig.test.json nyc mocha --config .mocharc.json \"src/**/*.spec.ts\"", "test": "npm run lint && npm run test-lint && npm run mocha && npm run test:types", "test:types": "tsd", @@ -51,7 +51,6 @@ "axios": "^0.19.0", "express": "^4.16.4", "please-upgrade-node": "^3.2.0", - "prettier": "^2.0.5", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" @@ -71,6 +70,7 @@ "eslint-plugin-jsdoc": "^28.5.1", "mocha": "^6.1.4", "nyc": "^14.0.0", + "prettier": "^2.0.5", "rewiremock": "^3.13.4", "shx": "^0.3.2", "sinon": "^7.3.1", diff --git a/src/App.spec.ts b/src/App.spec.ts index 1b841e80f..03e7dac40 100644 --- a/src/App.spec.ts +++ b/src/App.spec.ts @@ -12,9 +12,11 @@ import { WebClientOptions, WebClient } from '@slack/web-api'; // TODO: swap out rewiremock for proxyquire to see if it saves execution time // Utility functions -const noop = (() => Promise.resolve(undefined)); -const noopMiddleware = async ({ next }: { next: NextFn; }) => { await next!(); }; -const noopAuthorize = (() => Promise.resolve({})); +const noop = () => Promise.resolve(undefined); +const noopMiddleware = async ({ next }: { next: NextFn }) => { + await next(); +}; +const noopAuthorize = () => Promise.resolve({}); // Dummies (values that have no real behavior but pass through the system opaquely) function createDummyReceiverEvent(type: string = 'dummy_event_type'): ReceiverEvent { @@ -65,20 +67,19 @@ describe('App', () => { assert(authorizeCallback.notCalled, 'Should not call the authorize callback on instantiation'); assert.instanceOf(app, App); }); - it('should fail without a token for single team authorization or authorize callback or oauth installer', - async () => { - // Arrange - const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match + it('should fail without a token for single team authorization or authorize callback or oauth installer', async () => { + // Arrange + const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match - // Act - try { - new App({ signingSecret: '' }); // eslint-disable-line @typescript-eslint/no-unused-expressions - assert.fail(); - } catch (error) { - // Assert - assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); - } - }); + // Act + try { + new App({ signingSecret: '' }); // eslint-disable-line @typescript-eslint/no-unused-expressions + assert.fail(); + } catch (error) { + // Assert + assert.propertyVal(error, 'code', ErrorCode.AppInitializationError); + } + }); it('should fail when both a token and authorize callback are specified', async () => { // Arrange const authorizeCallback = sinon.fake(); @@ -198,7 +199,7 @@ describe('App', () => { withNoopWebClient(), withConversationContext(fakeConversationContext), ); - const dummyConvoStore = Symbol() as unknown as ConversationStore; + const dummyConvoStore = (Symbol() as unknown) as ConversationStore; const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match // Act @@ -211,23 +212,20 @@ describe('App', () => { }); it('with clientOptions', async () => { const fakeConstructor = sinon.fake(); - const overrides = mergeOverrides( - withNoopAppMetadata(), - { - '@slack/web-api': { - WebClient: class { - constructor() { - fakeConstructor(...arguments); - } - }, + const overrides = mergeOverrides(withNoopAppMetadata(), { + '@slack/web-api': { + WebClient: class { + constructor() { + fakeConstructor(...arguments); + } }, }, - ); - + }); + const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const clientOptions = { slackApiUrl: 'proxy.slack.com' }; - + new App({ clientOptions, authorize: noopAuthorize, signingSecret: '', logLevel: LogLevel.ERROR }); // eslint-disable-line @typescript-eslint/no-unused-expressions assert.ok(fakeConstructor.called); @@ -284,7 +282,7 @@ describe('App', () => { describe('event processing', () => { let fakeReceiver: FakeReceiver; let fakeErrorHandler: SinonSpy; - let dummyAuthorizationResult: { botToken: string, botId: string }; + let dummyAuthorizationResult: { botToken: string; botId: string }; beforeEach(() => { fakeReceiver = new FakeReceiver(); @@ -297,10 +295,12 @@ describe('App', () => { function createInvalidReceiverEvents(): ReceiverEvent[] { // TODO: create many more invalid receiver events (fuzzing) - return [{ - body: {}, - ack: sinon.fake.resolves(undefined), - }]; + return [ + { + body: {}, + ack: sinon.fake.resolves(undefined), + }, + ]; } it('should warn and skip when processing a receiver event with unknown type (never crash)', async () => { @@ -313,7 +313,7 @@ describe('App', () => { // Act const app = new App({ receiver: fakeReceiver, logger: fakeLogger, authorize: noopAuthorize }); app.use(fakeMiddleware); - await Promise.all(invalidReceiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(invalidReceiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert(fakeErrorHandler.notCalled); @@ -357,10 +357,10 @@ describe('App', () => { beforeEach(async () => { const fakeConversationContext = sinon.fake.returns(noopMiddleware); const overrides = mergeOverrides( - withNoopAppMetadata(), - withNoopWebClient(), - withMemoryStore(sinon.fake()), - withConversationContext(fakeConversationContext), + withNoopAppMetadata(), + withNoopWebClient(), + withMemoryStore(sinon.fake()), + withConversationContext(fakeConversationContext), ); const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match @@ -508,7 +508,7 @@ describe('App', () => { describe('listener middleware', () => { let app: App; const eventType = 'some_event_type'; - const dummyReceiverEvent = createDummyReceiverEvent(eventType); + const dummyReceiverEvent = createDummyReceiverEvent(eventType); beforeEach(async () => { const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match @@ -524,7 +524,9 @@ describe('App', () => { const errorToThrow = new Error('listener error'); // Act - app.event(eventType, async () => { throw errorToThrow; }); + app.event(eventType, async () => { + throw errorToThrow; + }); await fakeReceiver.sendEvent(dummyReceiverEvent); // Assert @@ -538,7 +540,9 @@ describe('App', () => { // Arrange const errorsToThrow = [new Error('first listener error'), new Error('second listener error')]; function createThrowingListener(toBeThrown: Error): () => Promise { - return async () => { throw toBeThrown; }; + return async () => { + throw toBeThrown; + }; } // Act @@ -575,31 +579,37 @@ describe('App', () => { describe('routing', () => { function createReceiverEvents(): ReceiverEvent[] { return [ - { // IncomingEventType.Event (app.event) + { + // IncomingEventType.Event (app.event) ...baseEvent, body: { event: {}, }, }, - { // IncomingEventType.Command (app.command) + { + // IncomingEventType.Command (app.command) ...baseEvent, body: { command: '/COMMAND_NAME', }, }, - { // IncomingEventType.Action (app.action) + { + // IncomingEventType.Action (app.action) ...baseEvent, body: { type: 'block_actions', - actions: [{ - action_id: 'block_action_id', - }], + actions: [ + { + action_id: 'block_action_id', + }, + ], channel: {}, user: {}, team: {}, }, }, - { // IncomingEventType.Shortcut (app.shortcut) + { + // IncomingEventType.Shortcut (app.shortcut) ...baseEvent, body: { type: 'message_action', @@ -609,7 +619,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.Shortcut (app.shortcut) + { + // IncomingEventType.Shortcut (app.shortcut) ...baseEvent, body: { type: 'message_action', @@ -619,7 +630,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.Shortcut (app.shortcut) + { + // IncomingEventType.Shortcut (app.shortcut) ...baseEvent, body: { type: 'shortcut', @@ -629,7 +641,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.Shortcut (app.shortcut) + { + // IncomingEventType.Shortcut (app.shortcut) ...baseEvent, body: { type: 'shortcut', @@ -639,7 +652,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.Action (app.action) + { + // IncomingEventType.Action (app.action) ...baseEvent, body: { type: 'interactive_message', @@ -650,7 +664,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.Action with dialog submission (app.action) + { + // IncomingEventType.Action with dialog submission (app.action) ...baseEvent, body: { type: 'dialog_submission', @@ -660,7 +675,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.Action for an external_select block (app.options) + { + // IncomingEventType.Action for an external_select block (app.options) ...baseEvent, body: { type: 'block_suggestion', @@ -671,7 +687,8 @@ describe('App', () => { actions: [], }, }, - { // IncomingEventType.Action for "data_source": "external" in dialogs (app.options) + { + // IncomingEventType.Action for "data_source": "external" in dialogs (app.options) ...baseEvent, body: { type: 'dialog_suggestion', @@ -682,7 +699,8 @@ describe('App', () => { team: {}, }, }, - { // IncomingEventType.ViewSubmitAction (app.view) + { + // IncomingEventType.ViewSubmitAction (app.view) ...baseEvent, body: { type: 'view_submission', @@ -747,51 +765,81 @@ describe('App', () => { await ackFn(); await next!(); }); - app.shortcut({ callback_id: 'message_action_callback_id' }, async ({ }) => { await shortcutFn(); }); - app.shortcut( - { type: 'message_action', callback_id: 'another_message_action_callback_id' }, - async ({ }) => { await shortcutFn(); }); - app.shortcut( - { type: 'message_action', callback_id: 'does_not_exist' }, - async ({ }) => { await shortcutFn(); }); - app.shortcut({ callback_id: 'shortcut_callback_id' }, async ({ }) => { await shortcutFn(); }); - app.shortcut( - { type: 'shortcut', callback_id: 'another_shortcut_callback_id' }, - async ({ }) => { await shortcutFn(); }); - app.shortcut({ type: 'shortcut', callback_id: 'does_not_exist' }, async ({ }) => { await shortcutFn(); }); - app.action('block_action_id', async ({ }) => { await actionFn(); }); - app.action({ callback_id: 'interactive_message_callback_id' }, async ({ }) => { await actionFn(); }); - app.action({ callback_id: 'dialog_submission_callback_id' }, async ({ }) => { await actionFn(); }); - app.view('view_callback_id', async ({ }) => { await viewFn(); }); - app.view({ callback_id: 'view_callback_id', type: 'view_closed' }, async ({ }) => { await viewFn(); }); - app.options('external_select_action_id', async ({ }) => { await optionsFn(); }); - app.options({ callback_id: 'dialog_suggestion_callback_id' }, async ({ }) => { await optionsFn(); }); - - app.event('app_home_opened', async ({ }) => { /* noop */ }); - app.message('hello', async ({ }) => { /* noop */ }); - app.command('/echo', async ({ }) => { /* noop */ }); + app.shortcut({ callback_id: 'message_action_callback_id' }, async ({}) => { + await shortcutFn(); + }); + app.shortcut({ type: 'message_action', callback_id: 'another_message_action_callback_id' }, async ({}) => { + await shortcutFn(); + }); + app.shortcut({ type: 'message_action', callback_id: 'does_not_exist' }, async ({}) => { + await shortcutFn(); + }); + app.shortcut({ callback_id: 'shortcut_callback_id' }, async ({}) => { + await shortcutFn(); + }); + app.shortcut({ type: 'shortcut', callback_id: 'another_shortcut_callback_id' }, async ({}) => { + await shortcutFn(); + }); + app.shortcut({ type: 'shortcut', callback_id: 'does_not_exist' }, async ({}) => { + await shortcutFn(); + }); + app.action('block_action_id', async ({}) => { + await actionFn(); + }); + app.action({ callback_id: 'interactive_message_callback_id' }, async ({}) => { + await actionFn(); + }); + app.action({ callback_id: 'dialog_submission_callback_id' }, async ({}) => { + await actionFn(); + }); + app.view('view_callback_id', async ({}) => { + await viewFn(); + }); + app.view({ callback_id: 'view_callback_id', type: 'view_closed' }, async ({}) => { + await viewFn(); + }); + app.options('external_select_action_id', async ({}) => { + await optionsFn(); + }); + app.options({ callback_id: 'dialog_suggestion_callback_id' }, async ({}) => { + await optionsFn(); + }); + + app.event('app_home_opened', async ({}) => { + /* noop */ + }); + app.message('hello', async ({}) => { + /* noop */ + }); + app.command('/echo', async ({}) => { + /* noop */ + }); // invalid view constraints - const invalidViewConstraints1 = { + const invalidViewConstraints1 = ({ callback_id: 'foo', type: 'view_submission', unknown_key: 'should be detected', - } as any as ViewConstraints; - app.view(invalidViewConstraints1, async ({ }) => { /* noop */ }); + } as any) as ViewConstraints; + app.view(invalidViewConstraints1, async ({}) => { + /* noop */ + }); assert.isTrue(fakeLogger.error.called); fakeLogger.error = sinon.fake(); - const invalidViewConstraints2 = { + const invalidViewConstraints2 = ({ callback_id: 'foo', type: undefined, unknown_key: 'should be detected', - } as any as ViewConstraints; - app.view(invalidViewConstraints2, async ({ }) => { /* noop */ }); + } as any) as ViewConstraints; + app.view(invalidViewConstraints2, async ({}) => { + /* noop */ + }); assert.isTrue(fakeLogger.error.called); app.error(fakeErrorHandler); - await Promise.all(dummyReceiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.equal(actionFn.callCount, 3); @@ -819,13 +867,16 @@ describe('App', () => { await respond(responseText); }); app.error(fakeErrorHandler); - await fakeReceiver.sendEvent({ // IncomingEventType.Action (app.action) + await fakeReceiver.sendEvent({ + // IncomingEventType.Action (app.action) body: { type: 'block_actions', response_url: responseUrl, - actions: [{ - action_id: actionId, - }], + actions: [ + { + action_id: actionId, + }, + ], channel: {}, user: {}, team: {}, @@ -855,13 +906,16 @@ describe('App', () => { await respond(responseObject); }); app.error(fakeErrorHandler); - await fakeReceiver.sendEvent({ // IncomingEventType.Action (app.action) + await fakeReceiver.sendEvent({ + // IncomingEventType.Action (app.action) body: { type: 'block_actions', response_url: responseUrl, - actions: [{ - action_id: actionId, - }], + actions: [ + { + action_id: actionId, + }, + ], channel: {}, user: {}, team: {}, @@ -877,7 +931,6 @@ describe('App', () => { }); describe('logger', () => { - it('should be available in middleware/listener args', async () => { // Arrange const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match @@ -918,7 +971,7 @@ describe('App', () => { ]; // Act - await Promise.all(receiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(receiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.isTrue(fakeLogger.info.called); @@ -966,7 +1019,7 @@ describe('App', () => { ]; // Act - await Promise.all(receiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(receiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.isTrue(fakeLogger.info.called); @@ -976,18 +1029,17 @@ describe('App', () => { }); describe('client', () => { - it('should be available in middleware/listener args', async () => { // Arrange - const App = await importApp(mergeOverrides( // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match - withNoopAppMetadata(), - withSuccessfulBotUserFetchingWebClient('B123', 'U123'), - )); - const tokens = [ - 'xoxb-123', - 'xoxp-456', - 'xoxb-123', - ]; + // eslint-disable-next-line @typescript-eslint/naming-convention + const App = await importApp( + mergeOverrides( + // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match + withNoopAppMetadata(), + withSuccessfulBotUserFetchingWebClient('B123', 'U123'), + ), + ); + const tokens = ['xoxb-123', 'xoxp-456', 'xoxb-123']; const app = new App({ receiver: fakeReceiver, authorize: () => { @@ -1032,7 +1084,7 @@ describe('App', () => { const receiverEvents = [event, event, event]; // Act - await Promise.all(receiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(receiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.isUndefined(app.client.token); @@ -1045,7 +1097,7 @@ describe('App', () => { assert.strictEqual(clients[0], clients[2]); }); - it('should be to the global app client when authorization doesn\'t produce a token', async () => { + it("should be to the global app client when authorization doesn't produce a token", async () => { // Arrange const App = await importApp(); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match const app = new App({ @@ -1057,7 +1109,9 @@ describe('App', () => { // Act let clientArg: WebClient | undefined; - app.use(async ({ client }) => { clientArg = client; }); + app.use(async ({ client }) => { + clientArg = client; + }); await fakeReceiver.sendEvent(createDummyReceiverEvent()); // Assert @@ -1066,7 +1120,6 @@ describe('App', () => { }); describe('say()', () => { - function createChannelContextualReceiverEvents(channelId: string): ReceiverEvent[] { return [ // IncomingEventType.Event with channel in payload @@ -1152,7 +1205,7 @@ describe('App', () => { await say(dummyMessage); }); app.error(fakeErrorHandler); - await Promise.all(dummyReceiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.equal(fakePostMessage.callCount, dummyReceiverEvents.length); @@ -1182,7 +1235,7 @@ describe('App', () => { await say(dummyMessage); }); app.error(fakeErrorHandler); - await Promise.all(dummyReceiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.equal(fakePostMessage.callCount, dummyReceiverEvents.length); @@ -1235,15 +1288,14 @@ describe('App', () => { { ...baseEvent, body: { - event: { - }, + event: {}, team_id: 'TEAM_ID', }, }, ]; } - it('should not exist in the arguments on incoming events that don\'t support say', async () => { + it("should not exist in the arguments on incoming events that don't support say", async () => { // Arrange const overrides = buildOverrides([withNoopWebClient()]); const App = await importApp(overrides); // eslint-disable-line @typescript-eslint/naming-convention, no-underscore-dangle, id-blacklist, id-match @@ -1260,7 +1312,7 @@ describe('App', () => { assertionAggregator(); }); - await Promise.all(dummyReceiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.equal(assertionAggregator.callCount, dummyReceiverEvents.length); @@ -1283,7 +1335,7 @@ describe('App', () => { await say(dummyMessage); }); app.error(fakeErrorHandler); - await Promise.all(dummyReceiverEvents.map(event => fakeReceiver.sendEvent(event))); + await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event))); // Assert assert.equal(fakeErrorHandler.callCount, dummyReceiverEvents.length); @@ -1306,7 +1358,7 @@ async function importApp( function withNoopWebClient(): Override { return { '@slack/web-api': { - WebClient: class { }, + WebClient: class {}, }, }; } @@ -1386,15 +1438,21 @@ function withConversationContext(spy: SinonSpy): Override { class FakeReceiver implements Receiver { private bolt: App | undefined; - public init = (bolt: App) => { this.bolt = bolt; }; + public init = (bolt: App) => { + this.bolt = bolt; + }; - public start = sinon.fake((...params: any[]): Promise => { - return Promise.resolve([...params]); - }); + public start = sinon.fake( + (...params: any[]): Promise => { + return Promise.resolve([...params]); + }, + ); - public stop = sinon.fake((...params: any[]): Promise => { - return Promise.resolve([...params]); - }); + public stop = sinon.fake( + (...params: any[]): Promise => { + return Promise.resolve([...params]); + }, + ); public async sendEvent(event: ReceiverEvent): Promise { return this.bolt?.processEvent(event); diff --git a/src/App.ts b/src/App.ts index ed58f11e2..cd89f3d82 100644 --- a/src/App.ts +++ b/src/App.ts @@ -2,12 +2,7 @@ import { Agent } from 'http'; import { SecureContextOptions } from 'tls'; import util from 'util'; -import { - WebClient, - ChatPostMessageArguments, - addAppMetadata, - WebClientOptions, -} from '@slack/web-api'; +import { WebClient, ChatPostMessageArguments, addAppMetadata, WebClientOptions } from '@slack/web-api'; import { Logger, LogLevel, ConsoleLogger } from '@slack/logger'; import axios, { AxiosInstance } from 'axios'; import ExpressReceiver, { ExpressReceiverOptions } from './ExpressReceiver'; @@ -50,12 +45,7 @@ import { RespondArguments, } from './types'; import { IncomingEventType, getTypeAndConversation, assertNever } from './helpers'; -import { - CodedError, - asCodedError, - AppInitializationError, - MultipleListenerError, -} from './errors'; +import { CodedError, asCodedError, AppInitializationError, MultipleListenerError } from './errors'; import allSettled = require('promise.allsettled'); // eslint-disable-line @typescript-eslint/no-require-imports const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires @@ -88,10 +78,7 @@ export { LogLevel, Logger } from '@slack/logger'; /** Authorization function - seeds the middleware processing and listeners with an authorization context */ export interface Authorize { - ( - source: AuthorizeSourceData, - body?: AnyMiddlewareArgs['body'], - ): Promise; + (source: AuthorizeSourceData, body?: AnyMiddlewareArgs['body']): Promise; } /** Authorization function inputs - authenticated data about an event for the authorization function */ @@ -114,9 +101,9 @@ export interface AuthorizeResult { export interface ActionConstraints { type?: A['type']; - block_id?: A extends BlockAction ? (string | RegExp) : never; - action_id?: A extends BlockAction ? (string | RegExp) : never; - callback_id?: Extract extends any ? (string | RegExp) : never; + block_id?: A extends BlockAction ? string | RegExp : never; + action_id?: A extends BlockAction ? string | RegExp : never; + callback_id?: Extract extends any ? string | RegExp : never; } export interface ShortcutConstraints { @@ -150,7 +137,6 @@ class WebClientPool { * A Slack App */ export default class App { - /** Slack Web API client */ public client: WebClient; @@ -200,7 +186,6 @@ export default class App { scopes = undefined, installerOptions = undefined, }: AppOptions = {}) { - if (typeof logger === 'undefined') { // Initialize with the default logger const consoleLogger = new ConsoleLogger(); @@ -223,13 +208,15 @@ export default class App { // the public WebClient instance (app.client) - this one doesn't have a token this.client = new WebClient(undefined, this.clientOptions); - this.axios = axios.create(Object.assign( - { - httpAgent: agent, - httpsAgent: agent, - }, - clientTls, - )); + this.axios = axios.create( + Object.assign( + { + httpAgent: agent, + httpsAgent: agent, + }, + clientTls, + ), + ); this.middleware = []; this.listeners = []; @@ -241,7 +228,7 @@ export default class App { // No custom receiver if (signingSecret === undefined) { throw new AppInitializationError( - 'Signing secret not found, so could not initialize the default receiver. Set a signing secret or use a ' + + 'Signing secret not found, so could not initialize the default receiver. Set a signing secret or use a ' + 'custom receiver.', ); } else { @@ -262,8 +249,10 @@ export default class App { } let usingOauth = false; - if ((this.receiver as ExpressReceiver).installer !== undefined - && (this.receiver as ExpressReceiver).installer!.authorize !== undefined) { + if ( + (this.receiver as ExpressReceiver).installer !== undefined && + (this.receiver as ExpressReceiver).installer!.authorize !== undefined + ) { // This supports using the built in ExpressReceiver, declaring your own ExpressReceiver // and theoretically, doing a fully custom (non express) receiver that implements OAuth usingOauth = true; @@ -281,9 +270,7 @@ export default class App { `No token, no authorize options, and no oauth installer options provided. ${tokenUsage}`, ); } else if (authorize !== undefined && usingOauth) { - throw new AppInitializationError( - `Both authorize options and oauth installer options provided. ${tokenUsage}`, - ); + throw new AppInitializationError(`Both authorize options and oauth installer options provided. ${tokenUsage}`); } else if (authorize === undefined && usingOauth) { this.authorize = (this.receiver as ExpressReceiver).installer!.authorize as Authorize; } else if (authorize !== undefined && !usingOauth) { @@ -338,18 +325,14 @@ export default class App { eventName: EventType, ...listeners: Middleware>[] ): void { - this.listeners.push( - [onlyEvents, matchEventType(eventName), ...listeners] as Middleware[], - ); + this.listeners.push([onlyEvents, matchEventType(eventName), ...listeners] as Middleware[]); } // TODO: just make a type alias for Middleware> // TODO: maybe remove the first two overloads public message(...listeners: Middleware>[]): void; public message(pattern: string | RegExp, ...listeners: Middleware>[]): void; - public message( - ...patternsOrMiddleware: (string | RegExp | Middleware>)[] - ): void { + public message(...patternsOrMiddleware: (string | RegExp | Middleware>)[]): void { const messageMiddleware = patternsOrMiddleware.map((patternOrMiddleware) => { if (typeof patternOrMiddleware === 'string' || util.types.isRegExp(patternOrMiddleware)) { return matchMessage(patternOrMiddleware); @@ -357,32 +340,36 @@ export default class App { return patternOrMiddleware; }); - this.listeners.push( - [onlyEvents, matchEventType('message'), ...messageMiddleware] as Middleware[], - ); + this.listeners.push([onlyEvents, matchEventType('message'), ...messageMiddleware] as Middleware< + AnyMiddlewareArgs + >[]); } public shortcut( callbackId: string | RegExp, ...listeners: Middleware>[] ): void; - public shortcut = ShortcutConstraints>( - constraints: Constraints, - ...listeners: Middleware>>[] + public shortcut< + Shortcut extends SlackShortcut = SlackShortcut, + Constraints extends ShortcutConstraints = ShortcutConstraints + >( + constraints: Constraints, + ...listeners: Middleware>>[] ): void; - public shortcut = ShortcutConstraints>( - callbackIdOrConstraints: string | RegExp | Constraints, - ...listeners: Middleware>>[] + public shortcut< + Shortcut extends SlackShortcut = SlackShortcut, + Constraints extends ShortcutConstraints = ShortcutConstraints + >( + callbackIdOrConstraints: string | RegExp | Constraints, + ...listeners: Middleware>>[] ): void { const constraints: ShortcutConstraints = - (typeof callbackIdOrConstraints === 'string' || util.types.isRegExp(callbackIdOrConstraints)) ? - { callback_id: callbackIdOrConstraints } : callbackIdOrConstraints; + typeof callbackIdOrConstraints === 'string' || util.types.isRegExp(callbackIdOrConstraints) + ? { callback_id: callbackIdOrConstraints } + : callbackIdOrConstraints; // Fail early if the constraints contain invalid keys - const unknownConstraintKeys = Object.keys(constraints) - .filter(k => (k !== 'callback_id' && k !== 'type')); + const unknownConstraintKeys = Object.keys(constraints).filter((k) => k !== 'callback_id' && k !== 'type'); if (unknownConstraintKeys.length > 0) { this.logger.error( `Slack listener cannot be attached using unknown constraint keys: ${unknownConstraintKeys.join(', ')}`, @@ -390,9 +377,9 @@ export default class App { return; } - this.listeners.push( - [onlyShortcuts, matchConstraints(constraints), ...listeners] as Middleware[], - ); + this.listeners.push([onlyShortcuts, matchConstraints(constraints), ...listeners] as Middleware< + AnyMiddlewareArgs + >[]); } // NOTE: this is what's called a convenience generic, so that types flow more easily without casting. @@ -401,25 +388,31 @@ export default class App { actionId: string | RegExp, ...listeners: Middleware>[] ): void; - public action = ActionConstraints>( - constraints: Constraints, - // NOTE: Extract<> is able to return the whole union when type: undefined. Why? - ...listeners: Middleware>>[] - ): void; - public action = ActionConstraints>( - actionIdOrConstraints: string | RegExp | Constraints, - ...listeners: Middleware>>[] - ): void { + public action< + Action extends SlackAction = SlackAction, + Constraints extends ActionConstraints = ActionConstraints + >( + constraints: Constraints, + // NOTE: Extract<> is able to return the whole union when type: undefined. Why? + ...listeners: Middleware>>[] + ): void; + public action< + Action extends SlackAction = SlackAction, + Constraints extends ActionConstraints = ActionConstraints + >( + actionIdOrConstraints: string | RegExp | Constraints, + ...listeners: Middleware>>[] + ): void { // Normalize Constraints const constraints: ActionConstraints = - (typeof actionIdOrConstraints === 'string' || util.types.isRegExp(actionIdOrConstraints)) ? - { action_id: actionIdOrConstraints } : actionIdOrConstraints; + typeof actionIdOrConstraints === 'string' || util.types.isRegExp(actionIdOrConstraints) + ? { action_id: actionIdOrConstraints } + : actionIdOrConstraints; // Fail early if the constraints contain invalid keys - const unknownConstraintKeys = Object.keys(constraints) - .filter(k => (k !== 'action_id' && k !== 'block_id' && k !== 'callback_id' && k !== 'type')); + const unknownConstraintKeys = Object.keys(constraints).filter( + (k) => k !== 'action_id' && k !== 'block_id' && k !== 'callback_id' && k !== 'type', + ); if (unknownConstraintKeys.length > 0) { this.logger.error( `Action listener cannot be attached using unknown constraint keys: ${unknownConstraintKeys.join(', ')}`, @@ -427,16 +420,12 @@ export default class App { return; } - this.listeners.push( - [onlyActions, matchConstraints(constraints), ...listeners] as Middleware[], - ); + this.listeners.push([onlyActions, matchConstraints(constraints), ...listeners] as Middleware[]); } // TODO: should command names also be regex? public command(commandName: string, ...listeners: Middleware[]): void { - this.listeners.push( - [onlyCommands, matchCommandName(commandName), ...listeners] as Middleware[], - ); + this.listeners.push([onlyCommands, matchCommandName(commandName), ...listeners] as Middleware[]); } public options( @@ -452,12 +441,11 @@ export default class App { ...listeners: Middleware>[] ): void { const constraints: ActionConstraints = - (typeof actionIdOrConstraints === 'string' || util.types.isRegExp(actionIdOrConstraints)) ? - { action_id: actionIdOrConstraints } : actionIdOrConstraints; + typeof actionIdOrConstraints === 'string' || util.types.isRegExp(actionIdOrConstraints) + ? { action_id: actionIdOrConstraints } + : actionIdOrConstraints; - this.listeners.push( - [onlyOptions, matchConstraints(constraints), ...listeners] as Middleware[], - ); + this.listeners.push([onlyOptions, matchConstraints(constraints), ...listeners] as Middleware[]); } public view( @@ -470,13 +458,14 @@ export default class App { ): void; public view( callbackIdOrConstraints: string | RegExp | ViewConstraints, - ...listeners: Middleware>[]): void { + ...listeners: Middleware>[] + ): void { const constraints: ViewConstraints = - (typeof callbackIdOrConstraints === 'string' || util.types.isRegExp(callbackIdOrConstraints)) ? - { callback_id: callbackIdOrConstraints, type: 'view_submission' } : callbackIdOrConstraints; + typeof callbackIdOrConstraints === 'string' || util.types.isRegExp(callbackIdOrConstraints) + ? { callback_id: callbackIdOrConstraints, type: 'view_submission' } + : callbackIdOrConstraints; // Fail early if the constraints contain invalid keys - const unknownConstraintKeys = Object.keys(constraints) - .filter(k => (k !== 'callback_id' && k !== 'type')); + const unknownConstraintKeys = Object.keys(constraints).filter((k) => k !== 'callback_id' && k !== 'type'); if (unknownConstraintKeys.length > 0) { this.logger.error( `View listener cannot be attached using unknown constraint keys: ${unknownConstraintKeys.join(', ')}`, @@ -485,15 +474,13 @@ export default class App { } if (constraints.type !== undefined && !validViewTypes.includes(constraints.type)) { - this.logger.error( - `View listener cannot be attached using unknown view event type: ${constraints.type}`, - ); + this.logger.error(`View listener cannot be attached using unknown view event type: ${constraints.type}`); return; } - this.listeners.push( - [onlyViewActions, matchConstraints(constraints), ...listeners] as Middleware[], - ); + this.listeners.push([onlyViewActions, matchConstraints(constraints), ...listeners] as Middleware< + AnyMiddlewareArgs + >[]); } public error(errorHandler: ErrorHandler): void { @@ -536,8 +523,10 @@ export default class App { const createSay = (channelId: string): SayFn => { const token = selectToken(context); return (message: Parameters[0]) => { - const postMessageArguments: ChatPostMessageArguments = (typeof message === 'string') ? - { token, text: message, channel: channelId } : { ...message, token, channel: channelId }; + const postMessageArguments: ChatPostMessageArguments = + typeof message === 'string' + ? { token, text: message, channel: channelId } + : { ...message, token, channel: channelId }; return this.client.chat.postMessage(postMessageArguments); }; @@ -548,28 +537,30 @@ export default class App { // const listenerArgs: Partial = { const listenerArgs: Pick & { /** Say function might be set below */ - say?: SayFn + say?: SayFn; /** Respond function might be set below */ - respond?: RespondFn, + respond?: RespondFn; /** Ack function might be set below */ - ack?: AckFn, + ack?: AckFn; } = { body: bodyArg, payload: - (type === IncomingEventType.Event) ? - (bodyArg as SlackEventMiddlewareArgs['body']).event : - (type === IncomingEventType.ViewAction) ? - (bodyArg as SlackViewMiddlewareArgs['body']).view : - (type === IncomingEventType.Shortcut) ? - (bodyArg as SlackShortcutMiddlewareArgs['body']) : - (type === IncomingEventType.Action && - isBlockActionOrInteractiveMessageBody(bodyArg as SlackActionMiddlewareArgs['body'])) ? - (bodyArg as SlackActionMiddlewareArgs['body']).actions[0] : - (bodyArg as ( - Exclude | SlackActionMiddlewareArgs> - )['body']), + type === IncomingEventType.Event + ? (bodyArg as SlackEventMiddlewareArgs['body']).event + : type === IncomingEventType.ViewAction + ? (bodyArg as SlackViewMiddlewareArgs['body']).view + : type === IncomingEventType.Shortcut + ? (bodyArg as SlackShortcutMiddlewareArgs['body']) + : type === IncomingEventType.Action && + isBlockActionOrInteractiveMessageBody(bodyArg as SlackActionMiddlewareArgs['body']) + ? (bodyArg as SlackActionMiddlewareArgs['body']).actions[0] + : (bodyArg as ( + | Exclude< + AnyMiddlewareArgs, + SlackEventMiddlewareArgs | SlackActionMiddlewareArgs | SlackViewMiddlewareArgs + > + | SlackActionMiddlewareArgs> + )['body']), }; // Set aliases @@ -605,8 +596,7 @@ export default class App { // Set respond() utility if (body.response_url) { listenerArgs.respond = (response: string | RespondArguments): Promise => { - const validResponse: RespondArguments = - (typeof response === 'string') ? { text: response } : response; + const validResponse: RespondArguments = typeof response === 'string' ? { text: response } : response; return this.axios.post(body.response_url, validResponse); }; @@ -658,18 +648,19 @@ export default class App { this.logger, async () => // When the listener middleware chain is done processing, call the listener without a next fn - listener({ ...listenerArgs as AnyMiddlewareArgs, context, client, logger: this.logger }), + listener({ ...(listenerArgs as AnyMiddlewareArgs), context, client, logger: this.logger }), ); } }); const settledListenerResults = await allSettled(listenerResults); - const rejectedListenerResults = - settledListenerResults.filter(lr => lr.status === 'rejected') as allSettled.PromiseRejection[]; + const rejectedListenerResults = settledListenerResults.filter( + (lr) => lr.status === 'rejected', + ) as allSettled.PromiseRejection[]; if (rejectedListenerResults.length === 1) { throw rejectedListenerResults[0].reason; } else if (rejectedListenerResults.length > 1) { - throw new MultipleListenerError(rejectedListenerResults.map(rlr => rlr.reason)); + throw new MultipleListenerError(rejectedListenerResults.map((rlr) => rlr.reason)); } }, ); @@ -684,10 +675,10 @@ export default class App { private handleError(error: Error): Promise { return this.errorHandler(asCodedError(error)); } - } -const tokenUsage = 'Apps used in one workspace should be initialized with a token. Apps used in many workspaces ' + +const tokenUsage = + 'Apps used in one workspace should be initialized with a token. Apps used in many workspaces ' + 'should be initialized with oauth installer or authorize.'; const validViewTypes = ['view_closed', 'view_submission']; @@ -706,23 +697,55 @@ function buildSource( // tslint:disable:max-line-length const source: AuthorizeSourceData = { teamId: - ((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).team_id as string : - (type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team.id as string : - assertNever(type)), + type === IncomingEventType.Event || type === IncomingEventType.Command + ? ((body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).team_id as string) + : type === IncomingEventType.Action || + type === IncomingEventType.Options || + type === IncomingEventType.ViewAction || + type === IncomingEventType.Shortcut + ? ((body as ( + | SlackActionMiddlewareArgs + | SlackOptionsMiddlewareArgs + | SlackViewMiddlewareArgs + | SlackShortcutMiddlewareArgs + )['body']).team.id as string) + : assertNever(type), enterpriseId: - ((type === IncomingEventType.Event || type === IncomingEventType.Command) ? (body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).enterprise_id as string : - (type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs)['body']).team.enterprise_id as string : - undefined), + type === IncomingEventType.Event || type === IncomingEventType.Command + ? ((body as (SlackEventMiddlewareArgs | SlackCommandMiddlewareArgs)['body']).enterprise_id as string) + : type === IncomingEventType.Action || + type === IncomingEventType.Options || + type === IncomingEventType.ViewAction || + type === IncomingEventType.Shortcut + ? ((body as ( + | SlackActionMiddlewareArgs + | SlackOptionsMiddlewareArgs + | SlackViewMiddlewareArgs + | SlackShortcutMiddlewareArgs + )['body']).team.enterprise_id as string) + : undefined, userId: - ((type === IncomingEventType.Event) ? - ((typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'string') ? (body as SlackEventMiddlewareArgs['body']).event.user as string : - (typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'object') ? (body as SlackEventMiddlewareArgs['body']).event.user.id as string : - ((body as SlackEventMiddlewareArgs['body']).event.channel !== undefined && (body as SlackEventMiddlewareArgs['body']).event.channel.creator !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.channel.creator as string : - ((body as SlackEventMiddlewareArgs['body']).event.subteam !== undefined && (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by !== undefined) ? (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by as string : - undefined) : - (type === IncomingEventType.Action || type === IncomingEventType.Options || type === IncomingEventType.ViewAction || type === IncomingEventType.Shortcut) ? (body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs)['body']).user.id as string : - (type === IncomingEventType.Command) ? (body as SlackCommandMiddlewareArgs['body']).user_id as string : - undefined), + type === IncomingEventType.Event + ? typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'string' + ? ((body as SlackEventMiddlewareArgs['body']).event.user as string) + : typeof (body as SlackEventMiddlewareArgs['body']).event.user === 'object' + ? ((body as SlackEventMiddlewareArgs['body']).event.user.id as string) + : (body as SlackEventMiddlewareArgs['body']).event.channel !== undefined && + (body as SlackEventMiddlewareArgs['body']).event.channel.creator !== undefined + ? ((body as SlackEventMiddlewareArgs['body']).event.channel.creator as string) + : (body as SlackEventMiddlewareArgs['body']).event.subteam !== undefined && + (body as SlackEventMiddlewareArgs['body']).event.subteam.created_by !== undefined + ? ((body as SlackEventMiddlewareArgs['body']).event.subteam.created_by as string) + : undefined + : type === IncomingEventType.Action || + type === IncomingEventType.Options || + type === IncomingEventType.ViewAction || + type === IncomingEventType.Shortcut + ? ((body as (SlackActionMiddlewareArgs | SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs)['body']).user + .id as string) + : type === IncomingEventType.Command + ? ((body as SlackCommandMiddlewareArgs['body']).user_id as string) + : undefined, conversationId: channelId, }; // tslint:enable:max-line-length @@ -749,16 +772,15 @@ function singleTeamAuthorization( authorization: Partial & { botToken: Required['botToken'] }, ): Authorize { // TODO: warn when something needed isn't found - const identifiers: Promise<{ botUserId: string, botId: string }> = authorization.botUserId !== undefined && - authorization.botId !== undefined ? - Promise.resolve({ botUserId: authorization.botUserId, botId: authorization.botId }) : - client.auth.test({ token: authorization.botToken }) - .then((result) => { - return { - botUserId: (result.user_id as string), - botId: (result.bot_id as string), - }; - }); + const identifiers: Promise<{ botUserId: string; botId: string }> = + authorization.botUserId !== undefined && authorization.botId !== undefined + ? Promise.resolve({ botUserId: authorization.botUserId, botId: authorization.botId }) + : client.auth.test({ token: authorization.botToken }).then((result) => { + return { + botUserId: result.user_id as string, + botId: result.bot_id as string, + }; + }); return async () => { return Object.assign({ botToken: authorization.botToken }, await identifiers); diff --git a/src/ExpressReceiver.spec.ts b/src/ExpressReceiver.spec.ts index ab7202b99..30bc508ae 100644 --- a/src/ExpressReceiver.spec.ts +++ b/src/ExpressReceiver.spec.ts @@ -14,24 +14,40 @@ import ExpressReceiver, { describe('ExpressReceiver', () => { const noopLogger: Logger = { - debug(..._msg: any[]): void { /* noop */ }, - info(..._msg: any[]): void { /* noop */ }, - warn(..._msg: any[]): void { /* noop */ }, - error(..._msg: any[]): void { /* noop */ }, - setLevel(_level: LogLevel): void { /* noop */ }, - getLevel(): LogLevel { return LogLevel.DEBUG; }, - setName(_name: string): void { /* noop */ }, + debug(..._msg: any[]): void { + /* noop */ + }, + info(..._msg: any[]): void { + /* noop */ + }, + warn(..._msg: any[]): void { + /* noop */ + }, + error(..._msg: any[]): void { + /* noop */ + }, + setLevel(_level: LogLevel): void { + /* noop */ + }, + getLevel(): LogLevel { + return LogLevel.DEBUG; + }, + setName(_name: string): void { + /* noop */ + }, }; function buildResponseToVerify(result: any): Response { - return { + return ({ status: (code: number) => { result.code = code; - return { - send: () => { result.sent = true; }, - } as any as Response; + return ({ + send: () => { + result.sent = true; + }, + } as any) as Response; }, - } as any as Response; + } as any) as Response; } describe('constructor', () => { @@ -74,9 +90,15 @@ describe('ExpressReceiver', () => { const req = { body: { ssl_check: 1 } } as Request; let sent = false; // tslint:disable-next-line: no-object-literal-type-assertion - const resp = { send: () => { sent = true; } } as Response; + const resp = { + send: () => { + sent = true; + }, + } as Response; let errorResult: any; - const next = (error: any) => { errorResult = error; }; + const next = (error: any) => { + errorResult = error; + }; // Act respondToSslCheck(req, resp, next); @@ -92,9 +114,15 @@ describe('ExpressReceiver', () => { const req = { body: { type: 'block_actions' } } as Request; let sent = false; // tslint:disable-next-line: no-object-literal-type-assertion - const resp = { send: () => { sent = true; } } as Response; + const resp = { + send: () => { + sent = true; + }, + } as Response; let errorResult: any; - const next = (error: any) => { errorResult = error; }; + const next = (error: any) => { + errorResult = error; + }; // Act respondToSslCheck(req, resp, next); @@ -112,9 +140,15 @@ describe('ExpressReceiver', () => { const req = { body: { type: 'url_verification', challenge: 'this is it' } } as Request; let sentBody = undefined; // tslint:disable-next-line: no-object-literal-type-assertion - const resp = { json: (body) => { sentBody = body; } } as Response; + const resp = { + json: (body) => { + sentBody = body; + }, + } as Response; let errorResult: any; - const next = (error: any) => { errorResult = error; }; + const next = (error: any) => { + errorResult = error; + }; // Act respondToUrlVerification(req, resp, next); @@ -130,9 +164,15 @@ describe('ExpressReceiver', () => { const req = { body: { ssl_check: 1 } } as Request; let sentBody = undefined; // tslint:disable-next-line: no-object-literal-type-assertion - const resp = { json: (body) => { sentBody = body; } } as Response; + const resp = { + json: (body) => { + sentBody = body; + }, + } as Response; let errorResult: any; - const next = (error: any) => { errorResult = error; }; + const next = (error: any) => { + errorResult = error; + }; // Act respondToUrlVerification(req, resp, next); @@ -145,7 +185,6 @@ describe('ExpressReceiver', () => { }); describe('verifySignatureAndParseRawBody', () => { - let clock: SinonFakeTimers; beforeEach(() => { @@ -162,7 +201,8 @@ describe('ExpressReceiver', () => { const signingSecret = '8f742231b10e8888abcd99yyyzzz85a5'; const signature = 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'; const requestTimestamp = 1531420618; - const body = 'token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c'; + const body = + 'token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c'; function buildExpressRequest(): Request { const reqAsStream = new Readable(); @@ -196,7 +236,9 @@ describe('ExpressReceiver', () => { async function runWithValidRequest(req: Request, state: any): Promise { // Arrange const resp = buildResponseToVerify(state); - const next = (error: any) => { state.error = error; }; + const next = (error: any) => { + state.error = error; + }; // Act const verifier = verifySignatureAndParseRawBody(noopLogger, signingSecret); diff --git a/src/ExpressReceiver.ts b/src/ExpressReceiver.ts index 8d88d8cc8..4e076f846 100644 --- a/src/ExpressReceiver.ts +++ b/src/ExpressReceiver.ts @@ -17,9 +17,11 @@ import { InstallProvider, StateStore, InstallationStore, CallbackOptions } from export interface ExpressReceiverOptions { signingSecret: string; logger?: Logger; - endpoints?: string | { - [endpointType: string]: string; - }; + endpoints?: + | string + | { + [endpointType: string]: string; + }; processBeforeResponse?: boolean; clientId?: string; clientSecret?: string; @@ -44,7 +46,6 @@ interface InstallerOptions { * Receives HTTP requests with Events, Slash Commands, and Actions */ export default class ExpressReceiver implements Receiver { - /* Express app */ public app: Application; @@ -87,11 +88,10 @@ export default class ExpressReceiver implements Receiver { } if ( - clientId !== undefined - && clientSecret !== undefined - && (stateSecret !== undefined || installerOptions.stateStore !== undefined) + clientId !== undefined && + clientSecret !== undefined && + (stateSecret !== undefined || installerOptions.stateStore !== undefined) ) { - this.installer = new InstallProvider({ clientId, clientSecret, @@ -104,14 +104,13 @@ export default class ExpressReceiver implements Receiver { // Add OAuth routes to receiver if (this.installer !== undefined) { - const redirectUriPath = installerOptions.redirectUriPath === undefined ? - '/slack/oauth_redirect' : installerOptions.redirectUriPath; + const redirectUriPath = + installerOptions.redirectUriPath === undefined ? '/slack/oauth_redirect' : installerOptions.redirectUriPath; this.router.use(redirectUriPath, async (req, res) => { await this.installer!.handleCallback(req, res, installerOptions.callbackOptions); }); - const installPath = installerOptions.installPath === undefined ? - '/slack/install' : installerOptions.installPath; + const installPath = installerOptions.installPath === undefined ? '/slack/install' : installerOptions.installPath; this.router.get(installPath, async (_req, res, next) => { try { const url = await this.installer!.generateInstallUrl({ @@ -136,10 +135,12 @@ export default class ExpressReceiver implements Receiver { let isAcknowledged = false; setTimeout(() => { if (!isAcknowledged) { - this.logger.error('An incoming event was not acknowledged within 3 seconds. ' + - 'Ensure that the ack() argument is called in a listener.'); + this.logger.error( + 'An incoming event was not acknowledged within 3 seconds. ' + + 'Ensure that the ack() argument is called in a listener.', + ); } - // tslint:disable-next-line: align + // tslint:disable-next-line: align }, 3001); let storedResponse = undefined; @@ -246,12 +247,8 @@ export const respondToUrlVerification: RequestHandler = (req, res, next) => { * - Verify the request signature * - Parse request.body and assign the successfully parsed object to it. */ -export function verifySignatureAndParseRawBody( - logger: Logger, - signingSecret: string, -): RequestHandler { +export function verifySignatureAndParseRawBody(logger: Logger, signingSecret: string): RequestHandler { return async (req, res, next) => { - let stringBody: string; // On some environments like GCP (Google Cloud Platform), // req.body can be pre-parsed and be passed as req.rawBody here @@ -287,39 +284,32 @@ export function verifySignatureAndParseRawBody( } function logError(logger: Logger, message: string, error: any): void { - const logMessage = ('code' in error) - ? `${message} (code: ${error.code}, message: ${error.message})` - : `${message} (error: ${error})`; + const logMessage = + 'code' in error ? `${message} (code: ${error.code}, message: ${error.message})` : `${message} (error: ${error})`; logger.warn(logMessage); } function verifyRequestSignature( - signingSecret: string, - body: string, - signature: string | undefined, - requestTimestamp: string | undefined, + signingSecret: string, + body: string, + signature: string | undefined, + requestTimestamp: string | undefined, ): void { if (signature === undefined || requestTimestamp === undefined) { - throw new ReceiverAuthenticityError( - 'Slack request signing verification failed. Some headers are missing.', - ); + throw new ReceiverAuthenticityError('Slack request signing verification failed. Some headers are missing.'); } const ts = Number(requestTimestamp); if (isNaN(ts)) { - throw new ReceiverAuthenticityError( - 'Slack request signing verification failed. Timestamp is invalid.', - ); + throw new ReceiverAuthenticityError('Slack request signing verification failed. Timestamp is invalid.'); } // Divide current date to match Slack ts format // Subtract 5 minutes from current time - const fiveMinutesAgo = Math.floor(Date.now() / 1000) - (60 * 5); + const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5; if (ts < fiveMinutesAgo) { - throw new ReceiverAuthenticityError( - 'Slack request signing verification failed. Timestamp is too old.', - ); + throw new ReceiverAuthenticityError('Slack request signing verification failed. Timestamp is too old.'); } const hmac = crypto.createHmac('sha256', signingSecret); @@ -327,9 +317,7 @@ function verifyRequestSignature( hmac.update(`${version}:${ts}:${body}`); if (!tsscmp(hash, hmac.digest('hex'))) { - throw new ReceiverAuthenticityError( - 'Slack request signing verification failed. Signature mismatch.', - ); + throw new ReceiverAuthenticityError('Slack request signing verification failed. Signature mismatch.'); } } @@ -339,9 +327,9 @@ function verifyRequestSignature( * - Parse request.body and assign the successfully parsed object to it. */ function verifySignatureAndParseBody( - signingSecret: string, - body: string, - headers: Record, + signingSecret: string, + body: string, + headers: Record, ): AnyMiddlewareArgs['body'] { // *** Request verification *** const { @@ -350,20 +338,12 @@ function verifySignatureAndParseBody( 'content-type': contentType, } = headers; - verifyRequestSignature( - signingSecret, - body, - signature, - requestTimestamp, - ); + verifyRequestSignature(signingSecret, body, signature, requestTimestamp); return parseRequestBody(body, contentType); } -function parseRequestBody( - stringBody: string, - contentType: string | undefined, -): any { +function parseRequestBody(stringBody: string, contentType: string | undefined): any { if (contentType === 'application/x-www-form-urlencoded') { const parsedBody = querystring.parse(stringBody); diff --git a/src/conversation-store.spec.ts b/src/conversation-store.spec.ts index ba1ed3894..cf684baf7 100644 --- a/src/conversation-store.spec.ts +++ b/src/conversation-store.spec.ts @@ -21,12 +21,12 @@ describe('conversationContext middleware', () => { const { conversationContext } = await importConversationStore( withGetTypeAndConversation(fakeGetTypeAndConversation), ); - const fakeArgs = { + const fakeArgs = ({ body: {}, context: dummyContext, next: fakeNext, logger: fakeLogger, - } as unknown as MiddlewareArgs; + } as unknown) as MiddlewareArgs; // Act const middleware = conversationContext(fakeStore); @@ -57,12 +57,12 @@ describe('conversationContext middleware', () => { const { conversationContext } = await importConversationStore( withGetTypeAndConversation(fakeGetTypeAndConversation), ); - const fakeArgs = { + const fakeArgs = ({ body: {}, context: dummyContext, next: fakeNext, logger: fakeLogger, - } as unknown as MiddlewareArgs; + } as unknown) as MiddlewareArgs; // Act const middleware = conversationContext(fakeStore); @@ -96,12 +96,12 @@ describe('conversationContext middleware', () => { const { conversationContext } = await importConversationStore( withGetTypeAndConversation(fakeGetTypeAndConversation), ); - const fakeArgs = { + const fakeArgs = ({ body: {}, context: dummyContext, next: fakeNext, logger: fakeLogger, - } as unknown as MiddlewareArgs; + } as unknown) as MiddlewareArgs; // Act const middleware = conversationContext(fakeStore); @@ -196,10 +196,10 @@ describe('MemoryStore', () => { /* Testing Harness */ type MiddlewareArgs = AnyMiddlewareArgs & { - next: NextFn, - context: Context, - logger: Logger, - client: WebClient, + next: NextFn; + context: Context; + logger: Logger; + client: WebClient; }; interface DummyContext { @@ -208,9 +208,7 @@ interface DummyContext { } // Loading the system under test using overrides -async function importConversationStore( - overrides: Override = {}, -): Promise { +async function importConversationStore(overrides: Override = {}): Promise { return rewiremock.module(() => import('./conversation-store'), overrides); } @@ -244,7 +242,7 @@ function createFakeStore( // Type 'any[]' is not comparable to type '[string, any, (number | undefined)?]'. // 223 set: setSpy as SinonSpy, ReturnType>, // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - set: setSpy as unknown as SinonSpy, ReturnType>, - get: getSpy as unknown as SinonSpy, ReturnType>, + set: (setSpy as unknown) as SinonSpy, ReturnType>, + get: (getSpy as unknown) as SinonSpy, ReturnType>, }; } diff --git a/src/errors.spec.ts b/src/errors.spec.ts index ac56a2819..ad1ddc260 100644 --- a/src/errors.spec.ts +++ b/src/errors.spec.ts @@ -13,7 +13,6 @@ import { } from './errors'; describe('Errors', () => { - it('has errors matching codes', () => { const errorMap = { [ErrorCode.AppInitializationError]: new AppInitializationError(), diff --git a/src/errors.ts b/src/errors.ts index b626e06f8..a50682009 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -71,7 +71,9 @@ export class MultipleListenerError extends Error implements CodedError { public originals: Error[]; constructor(originals: Error[]) { - super('Multiple errors occurred while handling several listeners. The `originals` property contains an array of each error.'); + super( + 'Multiple errors occurred while handling several listeners. The `originals` property contains an array of each error.', + ); this.originals = originals; } diff --git a/src/helpers.ts b/src/helpers.ts index 81c1786e2..af775098d 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -27,14 +27,17 @@ export enum IncomingEventType { * This is analogous to WhenEventHasChannelContext and the conditional type that checks SlackAction for a channel * context. */ -export function getTypeAndConversation(body: any): { type?: IncomingEventType, conversationId?: string } { +export function getTypeAndConversation(body: any): { type?: IncomingEventType; conversationId?: string } { if (body.event !== undefined) { - const eventBody = (body as SlackEventMiddlewareArgs['body']); + const eventBody = body as SlackEventMiddlewareArgs['body']; return { type: IncomingEventType.Event, conversationId: - eventBody.event.channel !== undefined ? eventBody.event.channel : - eventBody.event.item !== undefined ? eventBody.event.item.channel : undefined, + eventBody.event.channel !== undefined + ? eventBody.event.channel + : eventBody.event.item !== undefined + ? eventBody.event.item.channel + : undefined, }; } if (body.command !== undefined) { @@ -44,14 +47,14 @@ export function getTypeAndConversation(body: any): { type?: IncomingEventType, c }; } if (body.name !== undefined || body.type === 'block_suggestion') { - const optionsBody = (body as SlackOptionsMiddlewareArgs['body']); + const optionsBody = body as SlackOptionsMiddlewareArgs['body']; return { type: IncomingEventType.Options, conversationId: optionsBody.channel !== undefined ? optionsBody.channel.id : undefined, }; } if (body.actions !== undefined || body.type === 'dialog_submission') { - const actionBody = (body as SlackActionMiddlewareArgs['body']); + const actionBody = body as SlackActionMiddlewareArgs['body']; return { type: IncomingEventType.Action, conversationId: actionBody.channel !== undefined ? actionBody.channel.id : undefined, @@ -63,7 +66,7 @@ export function getTypeAndConversation(body: any): { type?: IncomingEventType, c }; } if (body.type === 'message_action') { - const shortcutBody = (body as SlackShortcutMiddlewareArgs['body']); + const shortcutBody = body as SlackShortcutMiddlewareArgs['body']; return { type: IncomingEventType.Shortcut, conversationId: shortcutBody.channel !== undefined ? shortcutBody.channel.id : undefined, diff --git a/src/index.ts b/src/index.ts index f88e42741..8ee16b6b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,16 +14,10 @@ export { Logger, } from './App'; -export { - default as ExpressReceiver, - ExpressReceiverOptions, -} from './ExpressReceiver'; +export { default as ExpressReceiver, ExpressReceiverOptions } from './ExpressReceiver'; export * from './errors'; export * from './middleware/builtin'; export * from './types'; -export { - ConversationStore, - MemoryStore, -} from './conversation-store'; +export { ConversationStore, MemoryStore } from './conversation-store'; diff --git a/src/middleware/builtin.spec.ts b/src/middleware/builtin.spec.ts index 0e9ab3fbf..9099178ec 100644 --- a/src/middleware/builtin.spec.ts +++ b/src/middleware/builtin.spec.ts @@ -5,13 +5,7 @@ import sinon from 'sinon'; import { ErrorCode, ContextMissingPropertyError } from '../errors'; import { Override, createFakeLogger } from '../test-helpers'; import rewiremock from 'rewiremock'; -import { - SlackEventMiddlewareArgs, - NextFn, - Context, - MessageEvent, - SlackCommandMiddlewareArgs, -} from '../types'; +import { SlackEventMiddlewareArgs, NextFn, Context, MessageEvent, SlackCommandMiddlewareArgs } from '../types'; import { onlyCommands, onlyEvents, matchCommandName, matchEventType, subtype } from './builtin'; import { SlashCommand } from '../types/command'; import { SlackEvent, AppMentionEvent, BotMessageEvent } from '../types/events'; @@ -33,19 +27,19 @@ describe('matchMessage()', () => { } function matchesPatternTestCase( - pattern: string | RegExp, - matchingText: string, - buildFakeEvent: (content: string) => SlackEvent, - ): Mocha.AsyncFunc { + pattern: string | RegExp, + matchingText: string, + buildFakeEvent: (content: string) => SlackEvent, + ): Mocha.AsyncFunc { return async () => { // Arrange const dummyContext: DummyContext = {}; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, event: buildFakeEvent(matchingText), context: dummyContext, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { matchMessage } = await importBuiltin(); // Act @@ -66,19 +60,19 @@ describe('matchMessage()', () => { } function notMatchesPatternTestCase( - pattern: string | RegExp, - nonMatchingText: string, - buildFakeEvent: (content: string) => SlackEvent, - ): Mocha.AsyncFunc { + pattern: string | RegExp, + nonMatchingText: string, + buildFakeEvent: (content: string) => SlackEvent, + ): Mocha.AsyncFunc { return async () => { // Arrange const dummyContext = {}; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ event: buildFakeEvent(nonMatchingText), context: dummyContext, next: fakeNext, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { matchMessage } = await importBuiltin(); // Act @@ -96,11 +90,11 @@ describe('matchMessage()', () => { // Arrange const dummyContext = {}; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ event: createFakeMessageEvent([{ type: 'divider' }]), context: dummyContext, next: fakeNext, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { matchMessage } = await importBuiltin(); // Act @@ -165,11 +159,11 @@ describe('matchMessage()', () => { describe('directMention()', () => { it('should bail when the context does not provide a bot user ID', async () => { // Arrange - const fakeArgs = { + const fakeArgs = ({ next: () => Promise.resolve(), message: createFakeMessageEvent(), context: {}, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { directMention } = await importBuiltin(); // Act @@ -193,11 +187,11 @@ describe('directMention()', () => { const fakeBotUserId = 'B123456'; const messageText = `<@${fakeBotUserId}> hi`; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, message: createFakeMessageEvent(messageText), context: { botUserId: fakeBotUserId }, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { directMention } = await importBuiltin(); // Act @@ -213,11 +207,11 @@ describe('directMention()', () => { const fakeBotUserId = 'B123456'; const messageText = 'hi'; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, message: createFakeMessageEvent(messageText), context: { botUserId: fakeBotUserId }, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { directMention } = await importBuiltin(); // Act @@ -233,11 +227,11 @@ describe('directMention()', () => { const fakeBotUserId = 'B123456'; const messageText = `hello <@${fakeBotUserId}>`; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, message: createFakeMessageEvent(messageText), context: { botUserId: fakeBotUserId }, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { directMention } = await importBuiltin(); // Act @@ -252,11 +246,11 @@ describe('directMention()', () => { // Arrange const fakeBotUserId = 'B123456'; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, message: createFakeMessageEvent([{ type: 'divider' }]), context: { botUserId: fakeBotUserId }, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { directMention } = await importBuiltin(); // Act @@ -272,11 +266,11 @@ describe('directMention()', () => { const fakeBotUserId = 'B123456'; const messageText = '<#C12345> hi'; const fakeNext = sinon.fake(); - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, message: createFakeMessageEvent(messageText), context: { botUserId: fakeBotUserId }, - } as unknown as MessageMiddlewareArgs; + } as unknown) as MessageMiddlewareArgs; const { directMention } = await importBuiltin(); // Act @@ -293,10 +287,10 @@ describe('ignoreSelf()', () => { // Arrange const fakeNext = sinon.fake.resolves(null); const fakeBotUserId = undefined; - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, context: { botUserId: fakeBotUserId, botId: fakeBotUserId }, - } as unknown as MemberJoinedOrLeftChannelMiddlewareArgs; + } as unknown) as MemberJoinedOrLeftChannelMiddlewareArgs; const { ignoreSelf: getIgnoreSelfMiddleware } = await importBuiltin(); @@ -320,17 +314,17 @@ describe('ignoreSelf()', () => { assert.equal(error.missingProperty, expectedError.missingProperty); }); - it('should immediately call next(), because incoming middleware args don\'t contain event', async () => { + it("should immediately call next(), because incoming middleware args don't contain event", async () => { // Arrange const fakeNext = sinon.fake(); const fakeBotUserId = 'BUSER1'; - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, context: { botUserId: fakeBotUserId, botId: fakeBotUserId }, command: { command: '/fakeCommand', }, - } as unknown as CommandMiddlewareArgs; + } as unknown) as CommandMiddlewareArgs; const { ignoreSelf: getIgnoreSelfMiddleware } = await importBuiltin(); @@ -372,18 +366,18 @@ describe('ignoreSelf()', () => { assert(fakeNext.notCalled); }); - it('should filter an event out, because it matches our own app and shouldn\'t be retained', async () => { + it("should filter an event out, because it matches our own app and shouldn't be retained", async () => { // Arrange const fakeNext = sinon.fake(); const fakeBotUserId = 'BUSER1'; - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, context: { botUserId: fakeBotUserId, botId: fakeBotUserId }, event: { type: 'tokens_revoked', user: fakeBotUserId, }, - } as unknown as TokensRevokedMiddlewareArgs; + } as unknown) as TokensRevokedMiddlewareArgs; const { ignoreSelf: getIgnoreSelfMiddleware } = await importBuiltin(); @@ -395,18 +389,18 @@ describe('ignoreSelf()', () => { assert(fakeNext.notCalled); }); - it('should filter an event out, because it matches our own app and shouldn\'t be retained', async () => { + it("should filter an event out, because it matches our own app and shouldn't be retained", async () => { // Arrange const fakeNext = sinon.fake(); const fakeBotUserId = 'BUSER1'; - const fakeArgs = { + const fakeArgs = ({ next: fakeNext, context: { botUserId: fakeBotUserId, botId: fakeBotUserId }, event: { type: 'tokens_revoked', user: fakeBotUserId, }, - } as unknown as TokensRevokedMiddlewareArgs; + } as unknown) as TokensRevokedMiddlewareArgs; const { ignoreSelf: getIgnoreSelfMiddleware } = await importBuiltin(); @@ -418,21 +412,21 @@ describe('ignoreSelf()', () => { assert(fakeNext.notCalled); }); - it('shouldn\'t filter an event out, because it should be retained', async () => { + it("shouldn't filter an event out, because it should be retained", async () => { // Arrange const fakeNext = sinon.fake(); const fakeBotUserId = 'BUSER1'; const eventsWhichShouldNotBeFilteredOut = ['member_joined_channel', 'member_left_channel']; const listOfFakeArgs = eventsWhichShouldNotBeFilteredOut.map((eventType) => { - return { + return ({ next: fakeNext, context: { botUserId: fakeBotUserId, botId: fakeBotUserId }, event: { type: eventType, user: fakeBotUserId, }, - } as unknown as MemberJoinedOrLeftChannelMiddlewareArgs; + } as unknown) as MemberJoinedOrLeftChannelMiddlewareArgs; }); const { ignoreSelf: getIgnoreSelfMiddleware } = await importBuiltin(); @@ -522,7 +516,6 @@ describe('matchCommandName', () => { }); describe('onlyEvents', () => { - const logger = createFakeLogger(); const client = new WebClient(undefined, { logger, slackApiUrl: undefined }); @@ -684,14 +677,14 @@ interface MiddlewareCommonArgs { type MessageMiddlewareArgs = SlackEventMiddlewareArgs<'message'> & MiddlewareCommonArgs; type TokensRevokedMiddlewareArgs = SlackEventMiddlewareArgs<'tokens_revoked'> & MiddlewareCommonArgs; -type MemberJoinedOrLeftChannelMiddlewareArgs = SlackEventMiddlewareArgs<'member_joined_channel' | 'member_left_channel'> - & MiddlewareCommonArgs; +type MemberJoinedOrLeftChannelMiddlewareArgs = SlackEventMiddlewareArgs< + 'member_joined_channel' | 'member_left_channel' +> & + MiddlewareCommonArgs; type CommandMiddlewareArgs = SlackCommandMiddlewareArgs & MiddlewareCommonArgs; -async function importBuiltin( - overrides: Override = {}, -): Promise { +async function importBuiltin(overrides: Override = {}): Promise { return rewiremock.module(() => import('./builtin'), overrides); } diff --git a/src/middleware/builtin.ts b/src/middleware/builtin.ts index 5f29c4e89..fcee9b242 100644 --- a/src/middleware/builtin.ts +++ b/src/middleware/builtin.ts @@ -44,7 +44,10 @@ export const onlyActions: Middleware = async ({ shortcut, next }) => { +export const onlyShortcuts: Middleware = async ({ + shortcut, + next, +}) => { // Filter out any non-shortcuts if (shortcut === undefined) { return; @@ -100,24 +103,26 @@ export const onlyEvents: Middleware /** * Middleware that filters out any event that isn't a view_submission or view_closed event */ -export const onlyViewActions: Middleware = async ({ view, next }) => { - // Filter out anything that doesn't have a view - if (view === undefined) { - return; - } +export const onlyViewActions: Middleware = async ({ + view, + next, +}) => { + // Filter out anything that doesn't have a view + if (view === undefined) { + return; + } - // It matches so we should continue down this middleware listener chain + // It matches so we should continue down this middleware listener chain // TODO: remove the non-null assertion operator - await next!(); - }; + await next!(); +}; /** * Middleware that checks for matches given constraints */ export function matchConstraints( - constraints: ActionConstraints | ViewConstraints | ShortcutConstraints, - ): Middleware { + constraints: ActionConstraints | ViewConstraints | ShortcutConstraints, +): Middleware { return async ({ payload, body, next, context }) => { // TODO: is putting matches in an array actually helpful? there's no way to know which of the regexps contributed // which matches (and in which order) @@ -131,7 +136,6 @@ export function matchConstraints( // Check block_id if (constraints.block_id !== undefined) { - if (typeof constraints.block_id === 'string') { if (payload.block_id !== constraints.block_id) { return; @@ -199,7 +203,7 @@ export function matchConstraints( if (body.type !== constraints.type) return; } - // TODO: remove the non-null assertion operator + // TODO: remove the non-null assertion operator await next!(); }; } @@ -208,8 +212,8 @@ export function matchConstraints( * Middleware that filters out messages that don't match pattern */ export function matchMessage( - pattern: string | RegExp, - ): Middleware> { + pattern: string | RegExp, +): Middleware> { return async ({ event, context, next }) => { let tempMatches: RegExpMatchArray | null; @@ -232,7 +236,7 @@ export function matchMessage( } } - // TODO: remove the non-null assertion operator + // TODO: remove the non-null assertion operator await next!(); }; } @@ -247,7 +251,7 @@ export function matchCommandName(name: string): Middleware { } const botId = args.context.botId as string; - const botUserId = args.context.botUserId !== undefined ? args.context.botUserId as string : undefined; + const botUserId = args.context.botUserId !== undefined ? (args.context.botUserId as string) : undefined; if (isEventArgs(args)) { // Once we've narrowed the type down to SlackEventMiddlewareArgs, there's no way to further narrow it down to @@ -296,10 +300,7 @@ export function ignoreSelf(): Middleware { // Its an Events API event that isn't of type message, but the user ID might match our own app. Filter these out. // However, some events still must be fired, because they can make sense. - const eventsWhichShouldBeKept = [ - 'member_joined_channel', - 'member_left_channel', - ]; + const eventsWhichShouldBeKept = ['member_joined_channel', 'member_left_channel']; const isEventShouldBeKept = eventsWhichShouldBeKept.includes(args.event.type); if (botUserId !== undefined && args.event.user === botUserId && !isEventShouldBeKept) { @@ -308,14 +309,15 @@ export function ignoreSelf(): Middleware { } // If all the previous checks didn't skip this message, then its okay to resume to next - // TODO: remove the non-null assertion operator + // TODO: remove the non-null assertion operator await args.next!(); }; } -export function subtype(subtype: string): Middleware> { // eslint-disable-line no-shadow +export function subtype(subtype1: string): Middleware> { + // eslint-disable-line no-shadow return async ({ message, next }) => { - if (message.subtype === subtype) { + if (message.subtype === subtype1) { // TODO: remove the non-null assertion operator await next!(); } @@ -346,12 +348,14 @@ export function directMention(): Middleware> matches === null || // stop when no matches are found matches.index !== 0 || // stop if match isn't at the beginning // stop if match isn't a user mention with the right user ID - matches.groups === undefined || matches.groups.type !== '@' || matches.groups.link !== context.botUserId + matches.groups === undefined || + matches.groups.type !== '@' || + matches.groups.link !== context.botUserId ) { return; } - // TODO: remove the non-null assertion operator + // TODO: remove the non-null assertion operator await next!(); }; } @@ -379,16 +383,11 @@ function isCallbackIdentifiedBody( } function isViewBody( - body: - SlackActionMiddlewareArgs['body'] - | SlackOptionsMiddlewareArgs['body'] - | SlackViewMiddlewareArgs['body'], + body: SlackActionMiddlewareArgs['body'] | SlackOptionsMiddlewareArgs['body'] | SlackViewMiddlewareArgs['body'], ): body is SlackViewAction { return (body as SlackViewAction).view !== undefined; } -function isEventArgs( - args: AnyMiddlewareArgs, -): args is SlackEventMiddlewareArgs { +function isEventArgs(args: AnyMiddlewareArgs): args is SlackEventMiddlewareArgs { return (args as SlackEventMiddlewareArgs).event !== undefined; } diff --git a/src/middleware/process.ts b/src/middleware/process.ts index 3ed778884..60056a2fe 100644 --- a/src/middleware/process.ts +++ b/src/middleware/process.ts @@ -1,8 +1,4 @@ -import { - Middleware, - AnyMiddlewareArgs, - Context, -} from '../types'; +import { Middleware, AnyMiddlewareArgs, Context } from '../types'; import { WebClient } from '@slack/web-api'; import { Logger } from '@slack/logger'; diff --git a/src/test-helpers.ts b/src/test-helpers.ts index 60c10f6e2..1f84979d7 100644 --- a/src/test-helpers.ts +++ b/src/test-helpers.ts @@ -53,9 +53,9 @@ export function createFakeLogger(): FakeLogger { // Property '0' is missing in type 'any[]' but required in type '[LogLevel]'. // 49 setLevel: sinon.fake() as SinonSpy, ReturnType>, // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - setLevel: sinon.fake() as unknown as SinonSpy, ReturnType>, - getLevel: sinon.fake() as unknown as SinonSpy, ReturnType>, - setName: sinon.fake() as unknown as SinonSpy, ReturnType>, + setLevel: (sinon.fake() as unknown) as SinonSpy, ReturnType>, + getLevel: (sinon.fake() as unknown) as SinonSpy, ReturnType>, + setName: (sinon.fake() as unknown) as SinonSpy, ReturnType>, debug: sinon.fake(), info: sinon.fake(), warn: sinon.fake(), diff --git a/src/types/actions/block-action.ts b/src/types/actions/block-action.ts index 025da4427..1831ea327 100644 --- a/src/types/actions/block-action.ts +++ b/src/types/actions/block-action.ts @@ -52,7 +52,7 @@ export interface ButtonAction extends BasicElementAction<'button'> { */ export interface StaticSelectAction extends BasicElementAction<'static_select'> { selected_option: { - text: PlainTextElement, + text: PlainTextElement; value: string; }; initial_option?: Option; @@ -66,9 +66,9 @@ export interface StaticSelectAction extends BasicElementAction<'static_select'> export interface MultiStaticSelectAction extends BasicElementAction<'multi_static_select'> { selected_options: [ { - text: PlainTextElement, + text: PlainTextElement; value: string; - } + }, ]; initial_options?: [Option]; placeholder?: PlainTextElement; @@ -162,7 +162,7 @@ export interface MultiExternalSelectAction extends BasicElementAction<'multi_ext */ export interface OverflowAction extends BasicElementAction<'overflow'> { selected_option: { - text: PlainTextElement, + text: PlainTextElement; value: string; }; confirm?: Confirmation; diff --git a/src/types/actions/index.ts b/src/types/actions/index.ts index 18dd3c71f..5b2f402ae 100644 --- a/src/types/actions/index.ts +++ b/src/types/actions/index.ts @@ -32,11 +32,11 @@ export type SlackAction = BlockAction | InteractiveMessage | DialogSubmitAction; * this case `ElementAction` must extend `BasicElementAction`. */ export interface SlackActionMiddlewareArgs { - payload: ( - Action extends BlockAction ? ElementAction : - Action extends InteractiveMessage ? InteractiveAction : - Action - ); + payload: Action extends BlockAction + ? ElementAction + : Action extends InteractiveMessage + ? InteractiveAction + : Action; action: this['payload']; body: Action; // all action types except dialog submission have a channel context @@ -49,8 +49,8 @@ export interface SlackActionMiddlewareArgs = - A extends InteractiveMessage ? AckFn : - A extends DialogSubmitAction ? AckFn : - // message action and block actions don't accept any value in the ack response - AckFn; +type ActionAckFn = A extends InteractiveMessage + ? AckFn + : A extends DialogSubmitAction + ? AckFn // message action and block actions don't accept any value in the ack response + : AckFn; diff --git a/src/types/actions/interactive-message.ts b/src/types/actions/interactive-message.ts index 67450212d..9653d940b 100644 --- a/src/types/actions/interactive-message.ts +++ b/src/types/actions/interactive-message.ts @@ -58,7 +58,7 @@ export interface InteractiveMessage = Extract; * Type function which tests whether or not the given `Event` contains a channel ID context for where the event * occurred, and returns `Type` when the test passes. Otherwise this returns `never`. */ -type WhenEventHasChannelContext = - Event extends ({ channel: string; } | { item: { channel: string; }; }) ? Type : never; +type WhenEventHasChannelContext = Event extends { channel: string } | { item: { channel: string } } + ? Type + : never; diff --git a/src/types/helpers.ts b/src/types/helpers.ts index c589d2b71..cb40779bd 100644 --- a/src/types/helpers.ts +++ b/src/types/helpers.ts @@ -7,11 +7,13 @@ export type StringIndexed = Record; * Type function which removes the index signature for the type `T` */ export type KnownKeys = { - [K in keyof T]: string extends K ? never : number extends K ? never : K -} extends { [_ in keyof T]: infer U } ? U : never; + [K in keyof T]: string extends K ? never : number extends K ? never : K; +} extends { [_ in keyof T]: infer U } + ? U + : never; /** * Type function which allows either types `T` or `U`, but not both. */ -export type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +export type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; type Without = { [P in Exclude]?: never }; diff --git a/src/types/middleware.ts b/src/types/middleware.ts index 373f5e227..8010fe430 100644 --- a/src/types/middleware.ts +++ b/src/types/middleware.ts @@ -10,8 +10,12 @@ import { Logger } from '@slack/logger'; // TODO: rename this to AnyListenerArgs, and all the constituent types export type AnyMiddlewareArgs = - SlackEventMiddlewareArgs | SlackActionMiddlewareArgs | SlackCommandMiddlewareArgs | - SlackOptionsMiddlewareArgs | SlackViewMiddlewareArgs | SlackShortcutMiddlewareArgs; + | SlackEventMiddlewareArgs + | SlackActionMiddlewareArgs + | SlackCommandMiddlewareArgs + | SlackOptionsMiddlewareArgs + | SlackViewMiddlewareArgs + | SlackShortcutMiddlewareArgs; interface AllMiddlewareArgs { context: Context; @@ -27,7 +31,6 @@ export interface Middleware { (args: Args & AllMiddlewareArgs): Promise; } -export interface Context extends StringIndexed { -} +export interface Context extends StringIndexed {} export type NextFn = () => Promise; diff --git a/src/types/options/index.ts b/src/types/options/index.ts index 25e0503b4..b37ac671c 100644 --- a/src/types/options/index.ts +++ b/src/types/options/index.ts @@ -40,7 +40,7 @@ export interface OptionsRequest ex name: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; callback_id: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; - action_ts: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; + action_ts: Source extends 'interactive_message' | 'dialog_suggestion' ? string : never; message_ts: Source extends 'interactive_message' ? string : never; attachment_id: Source extends 'interactive_message' ? string : never; @@ -63,10 +63,11 @@ export type OptionsSource = 'interactive_message' | 'dialog_suggestion' | 'block * Type function which given an options source `Source` returns a corresponding type for the `ack()` function. The * function is used to fulfill the options request from a listener or middleware. */ -type OptionsAckFn = - Source extends 'block_suggestion' ? AckFn>> : - Source extends 'interactive_message' ? AckFn>> : - AckFn>>; +type OptionsAckFn = Source extends 'block_suggestion' + ? AckFn>> + : Source extends 'interactive_message' + ? AckFn>> + : AckFn>>; interface BlockOptions { options: Option[]; diff --git a/src/types/utilities.ts b/src/types/utilities.ts index 9387a217e..69f884f86 100644 --- a/src/types/utilities.ts +++ b/src/types/utilities.ts @@ -11,10 +11,9 @@ export interface SayFn { (message: string | SayArguments): Promise; } -export type RespondArguments = -Pick< +export type RespondArguments = Pick< ChatPostMessageArguments, - Exclude, 'channel' | 'text' > + Exclude, 'channel' | 'text'> > & { /** Response URLs can be used to send ephemeral messages or in-channel messages using this argument */ response_type?: 'in_channel' | 'ephemeral'; diff --git a/src/types/view/index.ts b/src/types/view/index.ts index af86548db..6ac83b2b5 100644 --- a/src/types/view/index.ts +++ b/src/types/view/index.ts @@ -119,14 +119,16 @@ export interface ViewErrorsResponseAction { } export type ViewResponseAction = - ViewUpdateResponseAction | ViewPushResponseAction | ViewClearResponseAction | ViewErrorsResponseAction; + | ViewUpdateResponseAction + | ViewPushResponseAction + | ViewClearResponseAction + | ViewErrorsResponseAction; /** * Type function which given a view action `VA` returns a corresponding type for the `ack()` function. The function is * used to acknowledge the receipt (and possibly signal failure) of an view submission or closure from a listener or * middleware. */ -type ViewAckFn = - VA extends ViewSubmitAction ? AckFn : - // ViewClosedActions can only be acknowledged, there are no arguments - AckFn; +type ViewAckFn = VA extends ViewSubmitAction + ? AckFn // ViewClosedActions can only be acknowledged, there are no arguments + : AckFn; diff --git a/tsconfig.json b/tsconfig.json index cdcd897d3..e3a598405 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ /* Basic Options */ "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "resolveJsonModule": true, // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ @@ -60,7 +61,7 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, "include": [ - "src/**/*" + "src/**/*", ], "exclude": [ "**/*.spec.ts", From 13a1334c4162e50c67ba8d02c1755a86849232e9 Mon Sep 17 00:00:00 2001 From: Steve Gill Date: Thu, 23 Jul 2020 15:05:30 -0700 Subject: [PATCH 17/19] turned off a few custom lint rules to be more in line with the airbnb standard --- .eslintrc.js | 92 ++++++++++++++++----------------- src/App.ts | 63 +++++++++++----------- src/ExpressReceiver.ts | 18 ++++--- src/conversation-store.ts | 2 + src/errors.ts | 4 ++ src/index.ts | 2 +- src/middleware/builtin.ts | 8 ++- src/middleware/process.ts | 2 +- src/types/events/base-events.ts | 2 +- src/types/middleware.ts | 4 +- src/types/receiver.ts | 2 +- src/types/view/index.ts | 2 +- 12 files changed, 104 insertions(+), 97 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 45c83f24c..92adfdc9f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,63 +1,63 @@ module.exports = { - env: { - es6: true, - node: true, - }, - extends: [ - 'airbnb-typescript/base', - /* TODO: Uncomment rule below once jsdoc comments are added. + env: { + es6: true, + node: true, + }, + extends: [ + 'airbnb-typescript/base', + /* TODO: Uncomment rule below once jsdoc comments are added. This matches the jsdoc rules in the TSLint config */ - // "plugin:jsdoc/recommended", - 'prettier', - 'prettier/@typescript-eslint', - './eslint-config-base', // the common settings in eslint-config-base - ], - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.eslint.json', - }, - plugins: ['@typescript-eslint'], - ignorePatterns: ["**/*.spec.ts", "src/test-helpers.ts"], - rules: { + // "plugin:jsdoc/recommended", + 'prettier', + 'prettier/@typescript-eslint', + './eslint-config-base', // the common settings in eslint-config-base + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.eslint.json', + }, + plugins: ['@typescript-eslint'], + ignorePatterns: ['**/*.spec.ts', 'src/test-helpers.ts'], + rules: { /* Below are some of the new 'airbnb-typescript' rules that the project currently does not follow. They've been disabled here since they raise errors in a few files. The best course of action is likely to adopt these rules and make the quick (and mostly automated) fixes needed in the repo to conform to these. ESLint and the airbnb-typecript config is more strict than the original TSLint configuration that this project had. */ - 'import/first': ['off'], - 'import/prefer-default-export': ['off'], - 'import/newline-after-import': ['off'], - 'import/no-cycle': ['off'], - 'import/no-useless-path-segments': ['off'], - 'import/order': ['off'], - 'max-classes-per-file': ['off', 1], - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/lines-between-class-members': 'off', - 'no-nested-ternary': 'off', - 'no-restricted-globals': 'off', - 'no-lonely-if': 'off', - 'no-undef-init': 'off', - 'no-multi-assign': 'off', - 'prefer-object-spread': 'off', - 'consistent-return': 'off', - 'no-restricted-syntax': 'off', - 'prefer-destructuring': 'off', + 'import/first': ['off'], + 'import/prefer-default-export': ['off'], + 'max-classes-per-file': ['off', 1], + 'import/no-cycle': ['off'], + '@typescript-eslint/no-use-before-define': 'off', + 'no-nested-ternary': 'off', + 'consistent-return': 'off', + // 'import/order': ['off'], + // 'import/newline-after-import': ['off'], + // 'import/no-useless-path-segments': ['off'], + // '@typescript-eslint/lines-between-class-members': 'off', + // 'no-restricted-globals': 'off', + // 'no-lonely-if': 'off', + // 'no-undef-init': 'off', + // 'no-multi-assign': 'off', + // 'prefer-object-spread': 'off', + // 'no-restricted-syntax': 'off', + // 'prefer-destructuring': 'off', /* Some currently-enabled additional rules. Uncomment to disable. The project currently conforms to them so there it's best to just keep these commented or delete them entirely */ // '@typescript-eslint/ban-types': 'off', // '@typescript-eslint/no-empty-interface': 'off', // '@typescript-eslint/no-unsafe-assign': 'off', - // '@typescript-eslint/no-explicit-any': 'off', - // '@typescript-eslint/no-unsafe-member-access': 'off', - // '@typescript-eslint/no-unsafe-return': 'off', - // '@typescript-eslint/no-unnecessary-type-assertion': 'off', - // '@typescript-eslint/no-non-null-assertion': 'off', - // '@typescript-eslint/no-unsafe-assignment': 'off', + // '@typescript-eslint/no-explicit-any': 'off', + // '@typescript-eslint/no-unsafe-member-access': 'off', + // '@typescript-eslint/no-unsafe-return': 'off', + // '@typescript-eslint/no-unnecessary-type-assertion': 'off', + // '@typescript-eslint/no-non-null-assertion': 'off', + // '@typescript-eslint/no-unsafe-assignment': 'off', // '@typescript-eslint/no-unsafe-call': 'off', // '@typescript-eslint/restrict-template-expressions': 'off', - // '@typescript-eslint/unbound-method': 'off', - // '@typescript-eslint/explicit-module-boundary-types': 'off', + // '@typescript-eslint/unbound-method': 'off', + // '@typescript-eslint/explicit-module-boundary-types': 'off', // '@typescript-eslint/require-await': 'off', - }, + }, }; diff --git a/src/App.ts b/src/App.ts index cd89f3d82..f55eb6ea2 100644 --- a/src/App.ts +++ b/src/App.ts @@ -6,6 +6,7 @@ import { WebClient, ChatPostMessageArguments, addAppMetadata, WebClientOptions } import { Logger, LogLevel, ConsoleLogger } from '@slack/logger'; import axios, { AxiosInstance } from 'axios'; import ExpressReceiver, { ExpressReceiverOptions } from './ExpressReceiver'; +import packageJson from '../package.json'; import { ignoreSelf as ignoreSelfMiddleware, onlyActions, @@ -46,8 +47,8 @@ import { } from './types'; import { IncomingEventType, getTypeAndConversation, assertNever } from './helpers'; import { CodedError, asCodedError, AppInitializationError, MultipleListenerError } from './errors'; +// eslint-disable-next-line import/order import allSettled = require('promise.allsettled'); // eslint-disable-line @typescript-eslint/no-require-imports -const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires /** App initialization options */ export interface AppOptions { @@ -122,6 +123,7 @@ export interface ErrorHandler { class WebClientPool { private pool: { [token: string]: WebClient } = {}; + public getOrCreate(token: string, clientOptions: WebClientOptions): WebClient { const cachedClient = this.pool[token]; if (typeof cachedClient !== 'undefined') { @@ -208,15 +210,11 @@ export default class App { // the public WebClient instance (app.client) - this one doesn't have a token this.client = new WebClient(undefined, this.clientOptions); - this.axios = axios.create( - Object.assign( - { - httpAgent: agent, - httpsAgent: agent, - }, - clientTls, - ), - ); + this.axios = axios.create({ + httpAgent: agent, + httpsAgent: agent, + ...clientTls, + }); this.middleware = []; this.listeners = []; @@ -224,28 +222,26 @@ export default class App { // Check for required arguments of ExpressReceiver if (receiver !== undefined) { this.receiver = receiver; - } else { + } else if (signingSecret === undefined) { // No custom receiver - if (signingSecret === undefined) { - throw new AppInitializationError( - 'Signing secret not found, so could not initialize the default receiver. Set a signing secret or use a ' + - 'custom receiver.', - ); - } else { - // Create default ExpressReceiver - this.receiver = new ExpressReceiver({ - signingSecret, - endpoints, - processBeforeResponse, - clientId, - clientSecret, - stateSecret, - installationStore, - installerOptions, - scopes, - logger: this.logger, - }); - } + throw new AppInitializationError( + 'Signing secret not found, so could not initialize the default receiver. Set a signing secret or use a ' + + 'custom receiver.', + ); + } else { + // Create default ExpressReceiver + this.receiver = new ExpressReceiver({ + signingSecret, + endpoints, + processBeforeResponse, + clientId, + clientSecret, + stateSecret, + installationStore, + installerOptions, + scopes, + logger: this.logger, + }); } let usingOauth = false; @@ -611,11 +607,12 @@ export default class App { } // Get the client arg - let client = this.client; + let { client } = this; const token = selectToken(context); if (token !== undefined) { let pool = this.clients[source.teamId]; if (pool === undefined) { + // eslint-disable-next-line no-multi-assign pool = this.clients[source.teamId] = new WebClientPool(); } client = pool.getOrCreate(token, this.clientOptions); @@ -783,7 +780,7 @@ function singleTeamAuthorization( }); return async () => { - return Object.assign({ botToken: authorization.botToken }, await identifiers); + return { botToken: authorization.botToken, ...(await identifiers) }; }; } diff --git a/src/ExpressReceiver.ts b/src/ExpressReceiver.ts index 4e076f846..e8982ab96 100644 --- a/src/ExpressReceiver.ts +++ b/src/ExpressReceiver.ts @@ -1,16 +1,16 @@ /* eslint-disable @typescript-eslint/explicit-member-accessibility, @typescript-eslint/strict-boolean-expressions */ -import { AnyMiddlewareArgs, Receiver, ReceiverEvent } from './types'; import { createServer, Server } from 'http'; import express, { Request, Response, Application, RequestHandler, Router } from 'express'; import rawBody from 'raw-body'; import querystring from 'querystring'; import crypto from 'crypto'; import tsscmp from 'tsscmp'; -import App from './App'; -import { ReceiverAuthenticityError, ReceiverMultipleAckError } from './errors'; import { Logger, ConsoleLogger } from '@slack/logger'; import { InstallProvider, StateStore, InstallationStore, CallbackOptions } from '@slack/oauth'; +import App from './App'; +import { ReceiverAuthenticityError, ReceiverMultipleAckError } from './errors'; +import { AnyMiddlewareArgs, Receiver, ReceiverEvent } from './types'; // TODO: we throw away the key names for endpoints, so maybe we should use this interface. is it better for migrations? // if that's the reason, let's document that with a comment. @@ -50,10 +50,15 @@ export default class ExpressReceiver implements Receiver { public app: Application; private server: Server; + private bolt: App | undefined; + private logger: Logger; + private processBeforeResponse: boolean; + public router: Router; + public installer: InstallProvider | undefined = undefined; constructor({ @@ -83,9 +88,9 @@ export default class ExpressReceiver implements Receiver { this.logger = logger; const endpointList = typeof endpoints === 'string' ? [endpoints] : Object.values(endpoints); this.router = Router(); - for (const endpoint of endpointList) { + endpointList.forEach((endpoint) => { this.router.post(endpoint, ...expressMiddleware); - } + }); if ( clientId !== undefined && @@ -143,7 +148,7 @@ export default class ExpressReceiver implements Receiver { // tslint:disable-next-line: align }, 3001); - let storedResponse = undefined; + let storedResponse; const event: ReceiverEvent = { body: req.body, ack: async (response): Promise => { @@ -300,6 +305,7 @@ function verifyRequestSignature( } const ts = Number(requestTimestamp); + // eslint-disable-next-line no-restricted-globals if (isNaN(ts)) { throw new ReceiverAuthenticityError('Slack request signing verification failed. Timestamp is invalid.'); } diff --git a/src/conversation-store.ts b/src/conversation-store.ts index 223c5c825..958d845a0 100644 --- a/src/conversation-store.ts +++ b/src/conversation-store.ts @@ -18,12 +18,14 @@ export interface ConversationStore { */ export class MemoryStore implements ConversationStore { private state: Map = new Map(); + public set(conversationId: string, value: ConversationState, expiresAt?: number): Promise { return new Promise((resolve) => { this.state.set(conversationId, { value, expiresAt }); resolve(); }); } + public get(conversationId: string): Promise { return new Promise((resolve, reject) => { const entry = this.state.get(conversationId); diff --git a/src/errors.ts b/src/errors.ts index a50682009..9b4ea32d0 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -35,6 +35,7 @@ export class AppInitializationError extends Error implements CodedError { export class AuthorizationError extends Error implements CodedError { public code = ErrorCode.AuthorizationError; + public original: Error; constructor(message: string, original: Error) { @@ -46,6 +47,7 @@ export class AuthorizationError extends Error implements CodedError { export class ContextMissingPropertyError extends Error implements CodedError { public code = ErrorCode.ContextMissingPropertyError; + public missingProperty: string; constructor(missingProperty: string, message: string) { @@ -68,6 +70,7 @@ export class ReceiverAuthenticityError extends Error implements CodedError { export class MultipleListenerError extends Error implements CodedError { public code = ErrorCode.MultipleListenerError; + public originals: Error[]; constructor(originals: Error[]) { @@ -81,6 +84,7 @@ export class MultipleListenerError extends Error implements CodedError { export class UnknownError extends Error implements CodedError { public code = ErrorCode.UnknownError; + public original: Error; constructor(original: Error) { diff --git a/src/index.ts b/src/index.ts index 8ee16b6b5..d2550d8c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires import pleaseUpgradeNode from 'please-upgrade-node'; +import packageJson from '../package.json'; pleaseUpgradeNode(packageJson); diff --git a/src/middleware/builtin.ts b/src/middleware/builtin.ts index fcee9b242..466ebcfe6 100644 --- a/src/middleware/builtin.ts +++ b/src/middleware/builtin.ts @@ -175,12 +175,10 @@ export function matchConstraints( if (isViewBody(body)) { callbackId = body['view']['callback_id']; + } else if (isCallbackIdentifiedBody(body)) { + callbackId = body['callback_id']; } else { - if (isCallbackIdentifiedBody(body)) { - callbackId = body['callback_id']; - } else { - return; - } + return; } if (typeof constraints.callback_id === 'string') { diff --git a/src/middleware/process.ts b/src/middleware/process.ts index 60056a2fe..71ed831d9 100644 --- a/src/middleware/process.ts +++ b/src/middleware/process.ts @@ -1,6 +1,6 @@ -import { Middleware, AnyMiddlewareArgs, Context } from '../types'; import { WebClient } from '@slack/web-api'; import { Logger } from '@slack/logger'; +import { Middleware, AnyMiddlewareArgs, Context } from '../types'; export async function processMiddleware( middleware: Middleware[], diff --git a/src/types/events/base-events.ts b/src/types/events/base-events.ts index a47ce704d..26966c590 100644 --- a/src/types/events/base-events.ts +++ b/src/types/events/base-events.ts @@ -1,5 +1,5 @@ -import { StringIndexed } from '../helpers'; import { MessageAttachment, KnownBlock, Block, View } from '@slack/types'; +import { StringIndexed } from '../helpers'; /** * All known event types in Slack's Events API diff --git a/src/types/middleware.ts b/src/types/middleware.ts index 8010fe430..0e45f7146 100644 --- a/src/types/middleware.ts +++ b/src/types/middleware.ts @@ -1,3 +1,5 @@ +import { WebClient } from '@slack/web-api'; +import { Logger } from '@slack/logger'; import { StringIndexed } from './helpers'; import { SlackEventMiddlewareArgs } from './events'; import { SlackActionMiddlewareArgs } from './actions'; @@ -5,8 +7,6 @@ import { SlackCommandMiddlewareArgs } from './command'; import { SlackOptionsMiddlewareArgs } from './options'; import { SlackShortcutMiddlewareArgs } from './shortcuts'; import { SlackViewMiddlewareArgs } from './view'; -import { WebClient } from '@slack/web-api'; -import { Logger } from '@slack/logger'; // TODO: rename this to AnyListenerArgs, and all the constituent types export type AnyMiddlewareArgs = diff --git a/src/types/receiver.ts b/src/types/receiver.ts index c92ecc95e..466e55973 100644 --- a/src/types/receiver.ts +++ b/src/types/receiver.ts @@ -1,5 +1,5 @@ import App from '../App'; -import { AckFn } from '../types'; +import { AckFn } from './index'; import { StringIndexed } from './helpers'; export interface ReceiverEvent { diff --git a/src/types/view/index.ts b/src/types/view/index.ts index 6ac83b2b5..858b222ba 100644 --- a/src/types/view/index.ts +++ b/src/types/view/index.ts @@ -1,6 +1,6 @@ +import { View } from '@slack/types'; import { StringIndexed } from '../helpers'; import { AckFn } from '../utilities'; -import { View } from '@slack/types'; /** * Known view action types From 294348dc2e71d0dfac11b9fc790faf572f15e40b Mon Sep 17 00:00:00 2001 From: Steve Gill Date: Fri, 31 Jul 2020 13:20:06 -0700 Subject: [PATCH 18/19] reverted back to require for package.json --- src/App.ts | 3 ++- src/index.ts | 3 ++- tsconfig.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/App.ts b/src/App.ts index f55eb6ea2..696dd2429 100644 --- a/src/App.ts +++ b/src/App.ts @@ -6,7 +6,6 @@ import { WebClient, ChatPostMessageArguments, addAppMetadata, WebClientOptions } import { Logger, LogLevel, ConsoleLogger } from '@slack/logger'; import axios, { AxiosInstance } from 'axios'; import ExpressReceiver, { ExpressReceiverOptions } from './ExpressReceiver'; -import packageJson from '../package.json'; import { ignoreSelf as ignoreSelfMiddleware, onlyActions, @@ -49,6 +48,8 @@ import { IncomingEventType, getTypeAndConversation, assertNever } from './helper import { CodedError, asCodedError, AppInitializationError, MultipleListenerError } from './errors'; // eslint-disable-next-line import/order import allSettled = require('promise.allsettled'); // eslint-disable-line @typescript-eslint/no-require-imports +// eslint-disable-next-line @typescript-eslint/no-require-imports +const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires /** App initialization options */ export interface AppOptions { diff --git a/src/index.ts b/src/index.ts index d2550d8c7..ed5c02935 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import pleaseUpgradeNode from 'please-upgrade-node'; -import packageJson from '../package.json'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +const packageJson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires pleaseUpgradeNode(packageJson); diff --git a/tsconfig.json b/tsconfig.json index e3a598405..edd01815d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "resolveJsonModule": true, + // "resolveJsonModule": true, // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ From 0fbeb71bc4109764adab0a6d1d64385aced8367e Mon Sep 17 00:00:00 2001 From: Steve Gill Date: Wed, 5 Aug 2020 12:27:20 -0700 Subject: [PATCH 19/19] changed npm run lint command to run prettier --check. This gives us error messages on CI if prettier fails --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ac929d711..6c44e93b2 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "prepare": "npm run build", "build": "tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", - "lint": "prettier 'src/**/*.ts' --write && eslint --ext .ts src", - "test-lint": "eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" --fix && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\"", + "lint": "prettier 'src/**/*.ts' --check && eslint --ext .ts src", + "test-lint": "prettier 'src/**/*.spec.ts' --check && prettier 'src/test-helpers.ts' --check && eslint --no-eslintrc -c .eslintrc.test.js \"src/**/*.spec.ts\" --fix && eslint --no-eslintrc -c .eslintrc.test.js \"src/test-helpers.ts\"", "mocha": "TS_NODE_PROJECT=tsconfig.test.json nyc mocha --config .mocharc.json \"src/**/*.spec.ts\"", "test": "npm run lint && npm run test-lint && npm run mocha && npm run test:types", "test:types": "tsd",