diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da1cd55..4bdb732 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,15 +1,39 @@ on: - pull_request_target: + pull_request: push: branches: - '*' + tags: + - 'v*' jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 + + # Install dependencies - uses: actions/setup-node@v1 with: node-version: '12' + registry-url: 'https://registry.npmjs.org' - run: npm ci + + # Build + - run: npm pack + - uses: actions/upload-artifact@v2 + with: + name: npm package + path: ./ni-eslint-config-*.tgz + if-no-files-found: error + + # Test + - run: npm run test-typescript + - run: npm run test-typescript-typed + - run: npm run dev-print-typescript-props + + # Publish + - if: startsWith(github.ref, 'refs/tags/v') + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 0247ef7..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,18 +0,0 @@ -on: - push: - tags: - - 'v*' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: '12' - registry-url: 'https://registry.npmjs.org' - - run: npm ci - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 1faf560..263fea0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .DS_Store *.tgz +.vscode diff --git a/index.js b/index.js index 92ca264..b8107a4 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,11 @@ module.exports = { - extends: 'airbnb-base', + extends: [ + /* + airbnb-base source: + https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/index.js + */ + 'airbnb-base' + ], rules: { /* Omit arrow function parenthesis where they are not required to improve readability. @@ -57,11 +63,17 @@ module.exports = { */ 'linebreak-style': 'off', + /* + Requires empty lines between multiline class members but avoids the empty line + for single line members to reduce the amount of vertical space used in a class. + */ + 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], + /* Including one class per file is a best practice in general and also recommended by the Angular style guide. However, migrating older projects may not be trivial, and there may be exceptions for public/internal types that are only used as part of the interface - to the main type and no other types + to the main type and no other types. */ 'max-classes-per-file': ['error', 1], @@ -132,6 +144,9 @@ module.exports = { 'error', { selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', + }, { + selector: "UnaryExpression[operator='delete']", + message: 'The `delete` operator is not allowed. If using an object keys as a map, use the ES `Map` data structure instead.' }, { selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', diff --git a/lib/typescript-extensions-requiring-type-checking.js b/lib/typescript-extensions-requiring-type-checking.js new file mode 100644 index 0000000..7d449aa --- /dev/null +++ b/lib/typescript-extensions-requiring-type-checking.js @@ -0,0 +1,23 @@ +module.exports = { + rules: { + // Defined by Airbnb + // 'dot-notation': 'off', + // '@typescript-eslint/dot-notation': ['error', { allowKeywords: true }], + + // Defined by Airbnb + // 'no-implied-eval': 'off', + // '@typescript-eslint/no-implied-eval': 'error', + + // Defined by Airbnb + // 'no-throw-literal': 'off', + // '@typescript-eslint/no-throw-literal': 'error', + + // Defined by Airbnb + // 'require-await': 'off', + // '@typescript-eslint/require-await': 'off', + + // Defined by Airbnb + // 'no-return-await': 'off', + // '@typescript-eslint/return-await': 'error', + } +}; diff --git a/lib/typescript-extensions.js b/lib/typescript-extensions.js new file mode 100644 index 0000000..ac170c7 --- /dev/null +++ b/lib/typescript-extensions.js @@ -0,0 +1,170 @@ +module.exports = { + rules: { + /* + The following are extension rules that replace core JavaScript rules to support + TypeScript. + * When upgrading, changes to these rules can be identified in the typescript-eslint + changelog under features and breaking changes: + https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#extension-rules + * In addition, the `npm run dev-print-typescript-props` command can be used to list + the expected extension properties. + * The value of the extension properties should match the value chosen by the + JavaScript / Airbnb configuration. + */ + + // Defined by Airbnb + 'brace-style': 'off', + '@typescript-eslint/brace-style': ['error', '1tbs', { allowSingleLine: true }], + + // Defined by NI + 'comma-dangle': 'off', + '@typescript-eslint/comma-dangle': ['error', 'only-multiline'], + + // Defined by Airbnb + 'comma-spacing': 'off', + '@typescript-eslint/comma-spacing': ['error', { before: false, after: true }], + + // Defined by Airbnb + 'default-param-last': 'off', + '@typescript-eslint/default-param-last': 'off', + + // Defined by Airbnb + 'func-call-spacing': 'off', + '@typescript-eslint/func-call-spacing': ['error', 'never'], + + // Defined by NI + indent: 'off', + '@typescript-eslint/indent': ['error', 4], + + // Defined by Airbnb + 'init-declarations': 'off', + '@typescript-eslint/init-declarations': 'off', + + // Defined by Airbnb + 'keyword-spacing': 'off', + '@typescript-eslint/keyword-spacing': ['error', { + before: true, + after: true, + overrides: { + return: { after: true }, + throw: { after: true }, + case: { after: true } + } + }], + + // Defined by NI + 'lines-between-class-members': 'off', + '@typescript-eslint/lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], + + // Defined by Airbnb + 'no-array-constructor': 'off', + '@typescript-eslint/no-array-constructor': 'error', + + // Defined by Airbnb + 'no-dupe-class-members': 'off', + '@typescript-eslint/no-dupe-class-members': 'error', + + // Defined by Airbnb + 'no-duplicate-imports': 'off', + '@typescript-eslint/no-duplicate-imports': 'off', + + // Defined by Airbnb + 'no-empty-function': 'off', + '@typescript-eslint/no-empty-function': ['error', { + allow: [ + 'arrowFunctions', + 'functions', + 'methods' + ] + }], + + // Defined by Airbnb + 'no-extra-parens': 'off', + '@typescript-eslint/no-extra-parens': ['off', 'all', { + conditionalAssign: true, + nestedBinaryExpressions: false, + returnAssign: false, + ignoreJSX: 'all', + enforceForArrowConditionals: false, + }], + + // Defined by Airbnb + 'no-extra-semi': 'off', + '@typescript-eslint/no-extra-semi': 'error', + + // Defined by Airbnb + 'no-invalid-this': 'off', + '@typescript-eslint/no-invalid-this': 'off', + + // Defined by NI + 'no-loop-func': 'off', + '@typescript-eslint/no-loop-func': 'error', + + // Defined by Airbnb + 'no-loss-of-precision': 'off', + '@typescript-eslint/no-loss-of-precision': 'off', + + // Defined by Airbnb + 'no-magic-numbers': 'off', + '@typescript-eslint/no-magic-numbers': ['off', { + ignore: [], + ignoreArrayIndexes: true, + enforceConst: true, + detectObjects: false, + }], + + // Defined by Airbnb + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': 'error', + + // Defined by Airbnb + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', + + // Defined by Airbnb + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': ['error', { + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + }], + + // Defined by Airbnb + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }], + + // Defined by NI + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: true, variables: true }], + + // Defined by Airbnb + 'no-useless-constructor': 'off', + '@typescript-eslint/no-useless-constructor': 'error', + + // Available in newer typescript-eslint version, enable on upgrade. + // See: https://github.com/ni/javascript-styleguide/pull/18#discussion_r569795937 + // Defined by Airbnb + 'object-curly-spacing': 'off', + // '@typescript-eslint/object-curly-spacing': ['error', 'always'], + + // Defined by Airbnb + quotes: 'off', + '@typescript-eslint/quotes': ['error', 'single', { avoidEscape: true }], + + // Defined by Airbnb + semi: 'off', + '@typescript-eslint/semi': ['error', 'always'], + + // Defined by NI + 'space-before-function-paren': 'off', + '@typescript-eslint/space-before-function-paren': ['error', { + anonymous: 'always', + named: 'never', + asyncArrow: 'always' + }], + + // Defined by Airbnb + 'space-infix-ops': 'off', + '@typescript-eslint/space-infix-ops': 'error', + } +}; diff --git a/package-lock.json b/package-lock.json index d24893f..a3d8297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,12 +72,47 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz", + "integrity": "sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.9.1", + "@typescript-eslint/scope-manager": "4.9.1", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz", + "integrity": "sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.9.1", + "@typescript-eslint/types": "4.9.1", + "@typescript-eslint/typescript-estree": "4.9.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, "@typescript-eslint/parser": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.9.1.tgz", @@ -1577,6 +1612,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typescript": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", + "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/package.json b/package.json index ca4e795..fe18538 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "main": "index.js", "scripts": { "prepare": "npm test", - "test": "eslint ." + "test": "eslint .", + "test-typescript": "eslint ./test/typescript/", + "test-typescript-typed": "eslint ./test/typescript-type-checking/", + "dev-print-typescript-props": "node ./tools/print-typescript-properties" }, "repository": { "type": "git", @@ -26,16 +29,18 @@ "access": "public" }, "files": [ - ".eslintrc.js", - "typescript.js" + "/*.js", + "/lib/*" ], "dependencies": { "@typescript-eslint/parser": "^4.9.1", "eslint-config-airbnb-base": "~14.2.0" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.9.1", "eslint": "^7.6.0", - "eslint-plugin-import": "^2.22.0" + "eslint-plugin-import": "^2.22.0", + "typescript": "^4.1.3" }, "peerDependencies": { "@typescript-eslint/eslint-plugin": "^4.9.1", diff --git a/test/typescript-type-checking/.eslintrc.js b/test/typescript-type-checking/.eslintrc.js new file mode 100644 index 0000000..56ab480 --- /dev/null +++ b/test/typescript-type-checking/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + extends: '../../typescript-requiring-type-checking', + root: true, + ignorePatterns: ['*.js'], + parserOptions: { + project: ['./tsconfig.json'], + tsconfigRootDir: __dirname, + }, +}; diff --git a/test/typescript-type-checking/index.ts b/test/typescript-type-checking/index.ts new file mode 100644 index 0000000..027f543 --- /dev/null +++ b/test/typescript-type-checking/index.ts @@ -0,0 +1,13 @@ +// TypeScript Smoke Test + +export default class NI { + private _awesomeLevel = 1; + + public get awesome(): boolean { + return this._awesomeLevel > 0; + } + + public makeAwesomer(): void { + this._awesomeLevel += 1; + } +} diff --git a/test/typescript-type-checking/tsconfig.json b/test/typescript-type-checking/tsconfig.json new file mode 100644 index 0000000..f50bdf2 --- /dev/null +++ b/test/typescript-type-checking/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": ["./index.ts"], + "compilerOptions": { + "strictNullChecks": true, + "target": "ES2020" + }, +} \ No newline at end of file diff --git a/test/typescript/.eslintrc.js b/test/typescript/.eslintrc.js new file mode 100644 index 0000000..8f38443 --- /dev/null +++ b/test/typescript/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: '../../typescript', + root: true, +}; \ No newline at end of file diff --git a/test/typescript/index.ts b/test/typescript/index.ts new file mode 100644 index 0000000..027f543 --- /dev/null +++ b/test/typescript/index.ts @@ -0,0 +1,13 @@ +// TypeScript Smoke Test + +export default class NI { + private _awesomeLevel = 1; + + public get awesome(): boolean { + return this._awesomeLevel > 0; + } + + public makeAwesomer(): void { + this._awesomeLevel += 1; + } +} diff --git a/tools/print-typescript-properties.js b/tools/print-typescript-properties.js new file mode 100644 index 0000000..ddd5c7f --- /dev/null +++ b/tools/print-typescript-properties.js @@ -0,0 +1,38 @@ +const plugin = require('@typescript-eslint/eslint-plugin'); + +const isTrue = val => val !== undefined && val !== false; +const recommended = key => isTrue(plugin.rules[key].meta.docs.recommended); +const requiresTypeChecking = key => isTrue(plugin.rules[key].meta.docs.requiresTypeChecking); +const extendsBaseRule = key => isTrue(plugin.rules[key].meta.docs.extendsBaseRule); +const print = keys => { + const results = {}; + keys.forEach(key => { results[`@typescript-eslint/${key}`] = 'error'; }); + global.console.log(JSON.stringify(results, null, 4)); +}; + +const typeScriptExtensions = Object.keys(plugin.rules) + .filter(key => extendsBaseRule(key)) + .filter(key => !requiresTypeChecking(key)); + +const typeScriptExtensionsRequiringTypeChecks = Object.keys(plugin.rules) + .filter(key => extendsBaseRule(key)) + .filter(key => requiresTypeChecking(key)); + +const typeScript = Object.keys(plugin.rules) + .filter(key => !extendsBaseRule(key)) + .filter(key => !recommended(key)) + .filter(key => !requiresTypeChecking(key)); + +const typeScriptRequiringTypeChecks = Object.keys(plugin.rules) + .filter(key => !extendsBaseRule(key)) + .filter(key => !recommended(key)) + .filter(key => requiresTypeChecking(key)); + +global.console.log('TypeScript Extensions:'); +print(typeScriptExtensions); +global.console.log('TypeScript Extensions Requiring Type Checks:'); +print(typeScriptExtensionsRequiringTypeChecks); +global.console.log('Remaining TypeScript Rules:'); +print(typeScript); +global.console.log('Remaining TypeScript Rules Requiring Type Checks:'); +print(typeScriptRequiringTypeChecks); diff --git a/typescript-requiring-type-checking.js b/typescript-requiring-type-checking.js new file mode 100644 index 0000000..da1b8c0 --- /dev/null +++ b/typescript-requiring-type-checking.js @@ -0,0 +1,38 @@ +module.exports = { + extends: [ + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + './lib/typescript-extensions-requiring-type-checking' + ], + parser: '@typescript-eslint/parser', + rules: { + /* + Overrides to TypeScript recommended rules + https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/recommended-requiring-type-checking.ts + */ + + // None + + /* + TypeScript rules outside of the recommended configuration + */ + // '@typescript-eslint/naming-convention': 'error', + // '@typescript-eslint/no-base-to-string': 'error', + // '@typescript-eslint/no-confusing-void-expression': 'error', + // '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', + // '@typescript-eslint/no-unnecessary-condition': 'error', + // '@typescript-eslint/no-unnecessary-qualifier': 'error', + // '@typescript-eslint/no-unnecessary-type-arguments': 'error', + // Available in newer typescript-eslint version + // '@typescript-eslint/non-nullable-type-assertion-style': 'error', + // '@typescript-eslint/prefer-includes': 'error', + // '@typescript-eslint/prefer-nullish-coalescing': 'error', + // '@typescript-eslint/prefer-readonly': 'error', + // '@typescript-eslint/prefer-readonly-parameter-types': 'error', + // '@typescript-eslint/prefer-reduce-type-parameter': 'error', + // '@typescript-eslint/prefer-string-starts-ends-with': 'error', + // '@typescript-eslint/promise-function-async': 'error', + // '@typescript-eslint/require-array-sort-compare': 'error', + // '@typescript-eslint/strict-boolean-expressions': 'error', + // '@typescript-eslint/switch-exhaustiveness-check': 'error' + } +}; diff --git a/typescript.js b/typescript.js index 8f5fab6..784f6ef 100644 --- a/typescript.js +++ b/typescript.js @@ -2,79 +2,187 @@ module.exports = { extends: [ './index', 'plugin:@typescript-eslint/recommended', - 'plugin:import/typescript' + 'plugin:import/typescript', + './lib/typescript-extensions' ], parser: '@typescript-eslint/parser', rules: { /* - The following rules are already handled by the TypeScript compiler. + Overrides to import rules (already handled by the TypeScript compiler) + https://github.com/benmosher/eslint-plugin-import/blob/master/config/typescript.js */ 'import/named': 'off', 'import/no-unresolved': 'off', /* - The following are extension rules that replace core JavaScript rules to support - TypeScript. When upgrading, changes to these rules can be identified in the - typescript-eslint changelog under features and breaking changes. - https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#extension-rules + Overrides to TypeScript recommended rules + https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/recommended.ts */ - 'brace-style': 'off', - '@typescript-eslint/brace-style': ['error', '1tbs', { allowSingleLine: true }], - 'comma-dangle': 'off', - '@typescript-eslint/comma-dangle': ['error', 'only-multiline'], - 'comma-spacing': 'off', - '@typescript-eslint/comma-spacing': ['error'], - 'dot-notation': 'off', - '@typescript-eslint/dot-notation': 'error', - 'func-call-spacing': 'off', - '@typescript-eslint/func-call-spacing': 'error', - indent: 'off', - '@typescript-eslint/indent': ['error', 4], - 'keyword-spacing': 'off', - '@typescript-eslint/keyword-spacing': 'error', - 'lines-between-class-members': 'off', - '@typescript-eslint/lines-between-class-members': 'error', - 'no-array-constructor': 'off', - '@typescript-eslint/no-array-constructor': 'error', - 'no-dupe-class-members': 'off', - '@typescript-eslint/no-dupe-class-members': 'error', - 'no-empty-function': 'off', - '@typescript-eslint/no-empty-function': ['error', { - allow: [ - 'arrowFunctions', - 'functions', - 'methods' - ] + + // None + + /* + TypeScript rules outside of the recommended configuration + */ + + /* + Prefer the array straight bracket syntax over generics in all cases. + */ + '@typescript-eslint/array-type': 'error', + + '@typescript-eslint/ban-tslint-comment': 'error', + + '@typescript-eslint/class-literal-property-style': 'error', + + /* + Prefer the index signature syntax over the builtin `Record` type in all cases. + */ + '@typescript-eslint/consistent-indexed-object-style': ['error', 'index-signature'], + + '@typescript-eslint/consistent-type-assertions': 'error', + + '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], + + /* + Type imports are useful for uncommon use cases such as modules with + side-efects and file-by-file transpiling. Usage will be determined + on a case-by-case basis. + */ + '@typescript-eslint/consistent-type-imports': 'off', + + '@typescript-eslint/explicit-function-return-type': 'error', + + /* + Requiring an accessibility modifier helps when creating classes to ensure the + accessibility of a class member is intentionally decided and not relying on + the default of public accessibility. + */ + '@typescript-eslint/explicit-member-accessibility': 'error', + + /* + All interface members should be terminated with a semicolon including single line + definitions, consistent with classes. Object literal types should use commas + consistent with object literals. + */ + '@typescript-eslint/member-delimiter-style': ['error', { + overrides: { + interface: { + singleline: { + delimiter: 'semi', + requireLast: true + } + }, + typeLiteral: { + multiline: { + delimiter: 'comma', + requireLast: false + }, + singleline: { + delimiter: 'comma', + requireLast: false + } + } + } }], - 'no-extra-semi': 'off', - '@typescript-eslint/no-extra-semi': 'error', - 'no-loop-func': 'off', - '@typescript-eslint/no-loop-func': 'error', - 'no-redeclare': 'off', - '@typescript-eslint/no-redeclare': 'error', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'error', - 'no-unused-expressions': 'off', - '@typescript-eslint/no-unused-expressions': 'error', - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }], - 'no-use-before-define': 'off', - '@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: true, variables: true }], - 'no-useless-constructor': 'off', - '@typescript-eslint/no-useless-constructor': 'error', - quotes: 'off', - '@typescript-eslint/quotes': ['error', 'single', { avoidEscape: true }], - 'no-return-await': 'off', - '@typescript-eslint/return-await': 'error', - semi: 'off', - '@typescript-eslint/semi': 'error', - 'space-before-function-paren': 'off', - '@typescript-eslint/space-before-function-paren': ['error', { - anonymous: 'always', - named: 'never', - asyncArrow: 'always' + + /* + Group members by fields and methods and then order them by accessibility starting + with statics. Order members within these groups in a logical organization where + readability is the most important thing. Private fields that back public accessors + may be grouped with their accessor by disabling the rule with a comment. + */ + '@typescript-eslint/member-ordering': ['error', { + default: [ + 'signature', + 'public-static-field', + 'protected-static-field', + 'private-static-field', + 'static-field', + 'public-field', + 'protected-field', + 'private-field', + 'field', + 'public-constructor', + 'protected-constructor', + 'private-constructor', + 'constructor', + 'public-static-method', + 'protected-static-method', + 'private-static-method', + 'static-method', + 'public-method', + 'protected-method', + 'private-method', + 'method' + ] }], - 'space-infix-ops': 'off', - '@typescript-eslint/space-infix-ops': 'error', + + /* + The stricter type checking that's possible by using property style when declaring + method signatures only has benefits in some cases of inheritance. Therefore we + prefer to align the style between interface and class definitions and to align + with other languages used by NI like C#. + */ + '@typescript-eslint/method-signature-style': 'off', + + '@typescript-eslint/no-confusing-non-null-assertion': 'error', + + /* + This rule is unnecessary because delete is banned via 'no-restricted-syntax' + */ + '@typescript-eslint/no-dynamic-delete': 'off', + + '@typescript-eslint/no-extraneous-class': ['error', { allowWithDecorator: true, allowStaticOnly: true }], + + /* + TypeScript only supports catching `any` and `unknown` so this rule does not add + much value. With the rule no-throw-literal we enforce throwing `Error` objects, + which should be sufficient most of the time. For more type safety, consider + using type guards. + */ + '@typescript-eslint/no-implicit-any-catch': 'off', + + '@typescript-eslint/no-invalid-void-type': 'error', + + /* + Parameter properties are a nice shorthand for defining properties ingested + in the constructor. + */ + '@typescript-eslint/no-parameter-properties': 'off', + + '@typescript-eslint/no-require-imports': 'error', + + '@typescript-eslint/no-type-alias': 'off', + + '@typescript-eslint/no-unnecessary-type-constraint': 'error', + + '@typescript-eslint/no-unused-vars-experimental': 'off', + + /* + If an enum is crossing a code boundary (being serialized to JSON for example) then + you should initialize its values. But in most other cases code shouldn't care about + the values of enum items, so you can safely leave them implicit. + */ + '@typescript-eslint/prefer-enum-initializers': 'off', + + '@typescript-eslint/prefer-for-of': 'error', + + '@typescript-eslint/prefer-function-type': 'error', + + '@typescript-eslint/prefer-literal-enum-member': 'error', + + '@typescript-eslint/prefer-optional-chain': 'error', + + '@typescript-eslint/prefer-ts-expect-error': 'error', + + // Available in newer typescript-eslint version, disable on upgrade. + // See: https://github.com/ni/javascript-styleguide/pull/18#discussion_r575487604 + // '@typescript-eslint/sort-type-union-intersection-members': 'off', + + '@typescript-eslint/type-annotation-spacing': 'error', + + '@typescript-eslint/typedef': 'off', + + '@typescript-eslint/unified-signatures': 'error' } };