// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

const macros = require('./_macros');

const namingConventionRuleOptions = [
  {
    // We should be stricter about 'enumMember', but it often functions legitimately as an ad hoc namespace.
    selectors: ['variable', 'enumMember', 'function'],

    format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
    leadingUnderscore: 'allow',

    filter: {
      regex: [
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  {
    selectors: ['parameter'],

    format: ['camelCase'],

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  // Genuine properties
  {
    selectors: ['parameterProperty', 'accessor'],
    enforceLeadingUnderscoreWhenPrivate: true,

    format: ['camelCase', 'UPPER_CASE'],

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__',
        // Ignore quoted identifiers such as { "X+Y": 123 }.  Currently @typescript-eslint/naming-convention
        // cannot detect whether an identifier is quoted or not, so we simply assume that it is quoted
        // if-and-only-if it contains characters that require quoting.
        '[^a-zA-Z0-9_]',
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  // Properties that incorrectly match other contexts
  // See issue https://github.com/typescript-eslint/typescript-eslint/issues/2244
  {
    selectors: ['property'],
    enforceLeadingUnderscoreWhenPrivate: true,

    // The @typescript-eslint/naming-convention "property" selector matches cases like this:
    //
    //   someLegacyApiWeCannotChange.invokeMethod({ SomeProperty: 123 });
    //
    // and this:
    //
    //   const { CONSTANT1, CONSTANT2 } = someNamespace.constants;
    //
    // Thus for now "property" is more like a variable than a class member.
    format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
    leadingUnderscore: 'allow',

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__',
        // Ignore quoted identifiers such as { "X+Y": 123 }.  Currently @typescript-eslint/naming-convention
        // cannot detect whether an identifier is quoted or not, so we simply assume that it is quoted
        // if-and-only-if it contains characters that require quoting.
        '[^a-zA-Z0-9_]',
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  {
    selectors: ['method'],
    enforceLeadingUnderscoreWhenPrivate: true,

    // A PascalCase method can arise somewhat legitimately in this way:
    //
    // class MyClass {
    //    public static MyReactButton(props: IButtonProps): JSX.Element {
    //      . . .
    //    }
    // }
    format: ['camelCase', 'PascalCase'],
    leadingUnderscore: 'allow',

    filter: {
      regex: [
        // Silently accept names with a double-underscore prefix; we would like to be more strict about this,
        // pending a fix for https://github.com/typescript-eslint/typescript-eslint/issues/2240
        '^__',
        // This is a special exception for naming patterns that use an underscore to separate two camel-cased
        // parts.  Example:  "checkBox1_onChanged" or "_checkBox1_onChanged"
        '^_?[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*_[a-z][a-z0-9]*([A-Z][a-z]?[a-z0-9]*)*$'
      ]
        .map((x) => `(${x})`)
        .join('|'),
      match: false
    }
  },

  // Types should use PascalCase
  {
    // Group selector for: class, interface, typeAlias, enum, typeParameter
    selectors: ['class', 'typeAlias', 'enum', 'typeParameter'],
    format: ['PascalCase'],
    leadingUnderscore: 'allow'
  },

  {
    selectors: ['interface'],

    // It is very common for a class to implement an interface of the same name.
    // For example, the Widget class may implement the IWidget interface.  The "I" prefix
    // avoids the need to invent a separate name such as "AbstractWidget" or "WidgetInterface".
    // In TypeScript it is also common to declare interfaces that are implemented by primitive
    // objects, here the "I" prefix also helps by avoiding spurious conflicts with classes
    // by the same name.
    format: ['PascalCase'],

    custom: {
      regex: '^_?I[A-Z]',
      match: true
    }
  }
];

// Rule severity guidelines
// ------------------------
//
// Errors are generally printed in red, and may prevent other build tasks from running (e.g. unit tests).
// Developers should never ignore errors.  Warnings are generally printed in yellow, and do not block local
// development, although they must be fixed/suppressed before merging.  Developers will commonly ignore warnings
// until their feature is working.
//
// Rules that should be a WARNING:
// - An issue that is very common in partially implemented work (e.g. missing type declaration)
// - An issue that "keeps things nice" but otherwise doesn't affect the meaning of the code (e.g. naming convention)
// - Security rules -- developers may need to temporarily introduce "insecure" expressions while debugging;
//   if our policy forces them to suppress the lint rule, they may forget to reenable it later.
//
// Rules that should be an ERROR:
// - An issue that is very likely to be a typo (e.g. "x = x;")
// - An issue that catches code that is likely to malfunction (e.g. unterminated promise chain)
// - An obsolete language feature that nobody should be using for any good reason

function buildRules(profile) {
  return {
    // After an .eslintrc.js file is loaded, ESLint will normally continue visiting all parent folders
    // to look for other .eslintrc.js files, and also consult a personal file ~/.eslintrc.js.  If any files
    // are found, their options will be merged.  This is difficult for humans to understand, and it will cause
    // nondeterministic behavior if files are loaded from outside the Git working folder.
    //
    // Setting root=true causes ESLint to stop looking for other config files after the first .eslintrc.js
    // is loaded.
    root: true,

    // Disable the parser by default
    parser: '',

    plugins: [
      // Plugin documentation: https://www.npmjs.com/package/@rushstack/eslint-plugin
      '@rushstack/eslint-plugin',
      // Plugin documentation: https://www.npmjs.com/package/@rushstack/eslint-plugin-security
      '@rushstack/eslint-plugin-security',
      // Plugin documentation: https://www.npmjs.com/package/@typescript-eslint/eslint-plugin
      '@typescript-eslint/eslint-plugin',
      // Plugin documentation: https://www.npmjs.com/package/eslint-plugin-promise
      'eslint-plugin-promise'
    ],

    // Manually authored .d.ts files are generally used to describe external APIs that are  not expected
    // to follow our coding conventions.  Linting those files tends to produce a lot of spurious suppressions,
    // so we simply ignore them.
    ignorePatterns: ['*.d.ts'],

    overrides: [
      {
        // Declare an override that applies to TypeScript files only
        files: ['*.ts', '*.tsx'],
        parser: '@typescript-eslint/parser',
        parserOptions: {
          // The "project" path is resolved relative to parserOptions.tsconfigRootDir.
          // Your local .eslintrc.js must specify that parserOptions.tsconfigRootDir=__dirname.
          project: './tsconfig.json',

          // Allow parsing of newer ECMAScript constructs used in TypeScript source code.  Although tsconfig.json
          // may allow only a small subset of ES2018 features, this liberal setting ensures that ESLint will correctly
          // parse whatever is encountered.
          ecmaVersion: 2018,

          sourceType: 'module'
        },

        rules: {
          // ====================================================================
          // CUSTOM RULES
          // ====================================================================

          // The @rushstack rules are documented in the package README:
          // https://www.npmjs.com/package/@rushstack/eslint-plugin

          // RATIONALE:         See the @rushstack/eslint-plugin documentation
          '@rushstack/no-new-null': 'warn',

          // RATIONALE:         See the @rushstack/eslint-plugin documentation
          '@rushstack/typedef-var': 'warn',

          // RATIONALE:         See the @rushstack/eslint-plugin documentation
          //                    This is enabled and classified as an error because it is required when using Heft.
          //                    It's not required when using ts-jest, but still a good practice.
          '@rushstack/hoist-jest-mock': 'error',

          // ====================================================================
          // SECURITY RULES
          // ====================================================================

          // This is disabled for tools because, for example, it is a common and safe practice for a tool
          // to read a RegExp from a config file and use it to filter files paths.
          '@rushstack/security/no-unsafe-regexp': profile === 'node-trusted-tool' ? 'off' : 'warn',

          // ====================================================================
          // GENERAL RULES
          // ====================================================================

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/adjacent-overload-signatures': 'warn',

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-unsafe-function-type': 'warn',

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-wrapper-object-types': 'warn',

          // RATIONALE:         We require "x as number" instead of "<number>x" to avoid conflicts with JSX.
          '@typescript-eslint/consistent-type-assertions': 'warn',

          // RATIONALE:         We prefer "interface IBlah { x: number }" over "type Blah = { x: number }"
          //                    because code is more readable when it is built from stereotypical forms
          //                    (interfaces, enums, functions, etc.) instead of freeform type algebra.
          '@typescript-eslint/consistent-type-definitions': 'warn',

          // RATIONALE:         Code is more readable when the type of every variable is immediately obvious.
          //                    Even if the compiler may be able to infer a type, this inference will be unavailable
          //                    to a person who is reviewing a GitHub diff.  This rule makes writing code harder,
          //                    but writing code is a much less important activity than reading it.
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/explicit-function-return-type': [
            'warn',
            {
              allowExpressions: true,
              allowTypedFunctionExpressions: true,
              allowHigherOrderFunctions: false
            }
          ],

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/explicit-member-accessibility': 'warn',

          // RATIONALE:         Object-oriented programming organizes code into "classes" that associate
          //                    data structures (the class's fields) and the operations performed on those
          //                    data structures (the class's members).  Studying the fields often reveals the "idea"
          //                    behind a class.  The choice of which class a field belongs to may greatly impact
          //                    the code readability and complexity.  Thus, we group the fields prominently at the top
          //                    of the class declaration.  We do NOT enforce sorting based on public/protected/private
          //                    or static/instance, because these designations tend to change as code evolves, and
          //                    reordering methods produces spurious diffs that make PRs hard to read.  For classes
          //                    with lots of methods, alphabetization is probably a more useful secondary ordering.
          '@typescript-eslint/member-ordering': [
            'warn',
            {
              default: 'never',
              classes: ['field', 'constructor', 'method']
            }
          ],

          // NOTE: This new rule replaces several deprecated rules from @typescript-eslint/eslint-plugin@2.3.3:
          //
          // - @typescript-eslint/camelcase
          // - @typescript-eslint/class-name-casing
          // - @typescript-eslint/interface-name-prefix
          // - @typescript-eslint/member-naming
          //
          // Docs: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/naming-convention.md
          '@typescript-eslint/naming-convention': [
            'warn',
            ...macros.expandNamingConventionSelectors(namingConventionRuleOptions)
          ],

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-array-constructor': 'warn',

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          //
          // RATIONALE:         The "any" keyword disables static type checking, the main benefit of using TypeScript.
          //                    This rule should be suppressed only in very special cases such as JSON.stringify()
          //                    where the type really can be anything.  Even if the type is flexible, another type
          //                    may be more appropriate such as "unknown", "{}", or "Record<k,V>".
          '@typescript-eslint/no-explicit-any': 'warn',

          // RATIONALE:         The #1 rule of promises is that every promise chain must be terminated by a catch()
          //                    handler.  Thus wherever a Promise arises, the code must either append a catch handler,
          //                    or else return the object to a caller (who assumes this responsibility).  Unterminated
          //                    promise chains are a serious issue.  Besides causing errors to be silently ignored,
          //                    they can also cause a NodeJS process to terminate unexpectedly.
          '@typescript-eslint/no-floating-promises': 'error',

          // RATIONALE:         Catches a common coding mistake.
          '@typescript-eslint/no-for-in-array': 'error',

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-misused-new': 'error',

          // RATIONALE:         The "namespace" keyword is not recommended for organizing code because JavaScript lacks
          //                    a "using" statement to traverse namespaces.  Nested namespaces prevent certain bundler
          //                    optimizations.  If you are declaring loose functions/variables, it's better to make them
          //                    static members of a class, since classes support property getters and their private
          //                    members are accessible by unit tests.  Also, the exercise of choosing a meaningful
          //                    class name tends to produce more discoverable APIs: for example, search+replacing
          //                    the function "reverse()" is likely to return many false matches, whereas if we always
          //                    write "Text.reverse()" is more unique.  For large scale organization, it's recommended
          //                    to decompose your code into separate NPM packages, which ensures that component
          //                    dependencies are tracked more conscientiously.
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-namespace': [
            'warn',
            {
              // Discourage "namespace" in .ts and .tsx files
              allowDeclarations: false,

              // Allow it in .d.ts files that describe legacy libraries
              allowDefinitionFiles: false
            }
          ],

          // RATIONALE:         Parameter properties provide a shorthand such as "constructor(public title: string)"
          //                    that avoids the effort of declaring "title" as a field.  This TypeScript feature makes
          //                    code easier to write, but arguably sacrifices readability:  In the notes for
          //                    "@typescript-eslint/member-ordering" we pointed out that fields are central to
          //                    a class's design, so we wouldn't want to bury them in a constructor signature
          //                    just to save some typing.
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/parameter-properties': 'warn',

          // RATIONALE:         When left in shipping code, unused variables often indicate a mistake.  Dead code
          //                    may impact performance.
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-unused-vars': [
            'warn',
            {
              vars: 'all',
              // Unused function arguments often indicate a mistake in JavaScript code.  However in TypeScript code,
              // the compiler catches most of those mistakes, and unused arguments are fairly common for type signatures
              // that are overriding a base class method or implementing an interface.
              args: 'none',
              // Unused error arguments are common and useful for inspection when a debugger is attached.
              caughtErrors: 'none'
            }
          ],

          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/no-use-before-define': [
            'error',
            {
              // Base ESLint options

              // We set functions=false so that functions can be ordered based on exported/local visibility
              // similar to class methods.  Also the base lint rule incorrectly flags a legitimate case like:
              //
              //   function a(n: number): void {
              //     if (n > 0) {
              //       b(n-1); //   lint error
              //     }
              //   }
              //   function b(n: number): void {
              //     if (n > 0) {
              //       a(n-1);
              //     }
              //   }
              functions: false,
              classes: true,
              variables: true,

              // TypeScript extensions

              enums: true,
              typedefs: true
              // ignoreTypeReferences: true
            }
          ],

          // TODO: This is a good rule for web browser apps, but it is commonly needed API for Node.js tools.
          // '@typescript-eslint/no-var-requires': 'error',

          // RATIONALE:         The "module" keyword is deprecated except when describing legacy libraries.
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/prefer-namespace-keyword': 'warn',

          // RATIONALE:         We require explicit type annotations, even when the compiler could infer the type.
          //                    This can be a controversial policy because it makes code more verbose.  There are
          //                    a couple downsides to type inference, however.  First, it is not always available.
          //                    For example, when reviewing a pull request or examining a Git history, we may see
          //                    code like this:
          //
          //                        // What is the type of "y" here? The compiler knows, but the
          //                        // person reading the code may have no clue.
          //                        const x = f.();
          //                        const y = x.z;
          //
          //                    Second, relying on implicit types also discourages design discussions and documentation.
          //                    Consider this example:
          //
          //                        // Where's the documentation for "correlation" and "inventory"?
          //                        // Where would you even write the TSDoc comments?
          //                        function g() {
          //                          return { correlation: 123, inventory: 'xyz' };
          //                        }
          //
          //                    Implicit types make sense for small scale scenarios, where everyone is familiar with
          //                    the project, and code should be "easy to write".  Explicit types are preferable
          //                    for large scale scenarios, where people regularly work with source files they've never
          //                    seen before, and code should be "easy to read."
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          '@typescript-eslint/typedef': [
            'warn',
            {
              arrayDestructuring: false,
              arrowParameter: false,
              memberVariableDeclaration: true,
              objectDestructuring: false,
              parameter: true,
              propertyDeclaration: true,

              // This case is handled by our "@rushstack/typedef-var" rule
              variableDeclaration: false,

              // Normally we require type declarations for class members.  However, that rule is relaxed
              // for situations where we need to bind the "this" pointer for a callback.  For example, consider
              // this event handler for a React component:
              //
              //     class MyComponent {
              //       public render(): React.ReactNode {
              //          return (
              //            <a href="#" onClick={this._onClick}> click me </a>
              //          );
              //        }
              //
              //        // The assignment here avoids the need for "this._onClick.bind(this)"
              //        private _onClick = (event: React.MouseEvent<HTMLAnchorElement>): void => {
              //          console.log("Clicked! " + this.props.title);
              //        };
              //      }
              //
              // This coding style has limitations and should be used sparingly.  For example, "_onClick"
              // will not participate correctly in "virtual"/"override" inheritance.
              //
              // NOTE: This option affects both "memberVariableDeclaration" and "variableDeclaration" options.
              variableDeclarationIgnoreFunction: true
            }
          ],

          // RATIONALE:         This rule warns if setters are defined without getters, which is probably a mistake.
          'accessor-pairs': 'error',

          // RATIONALE:         In TypeScript, if you write x["y"] instead of x.y, it disables type checking.
          'dot-notation': [
            'warn',
            {
              allowPattern: '^_'
            }
          ],

          // RATIONALE:         Catches code that is likely to be incorrect
          eqeqeq: 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'for-direction': 'warn',

          // RATIONALE:         Catches a common coding mistake.
          'guard-for-in': 'error',

          // RATIONALE:         If you have more than 2,000 lines in a single source file, it's probably time
          //                    to split up your code.
          'max-lines': ['warn', { max: 2000 }],

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-async-promise-executor': 'error',

          // RATIONALE:         "|" and "&" are relatively rare, and are more likely to appear as a mistake when
          //                    someone meant "||" or "&&".  (But nobody types the other operators by mistake.)
          'no-bitwise': [
            'warn',
            {
              allow: [
                '^',
                // "|",
                // "&",
                '<<',
                '>>',
                '>>>',
                '^=',
                // "|=",
                //"&=",
                '<<=',
                '>>=',
                '>>>=',
                '~'
              ]
            }
          ],

          // RATIONALE:         Deprecated language feature.
          'no-caller': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-compare-neg-zero': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-cond-assign': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-constant-condition': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-control-regex': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-debugger': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-delete-var': 'error',

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-duplicate-case': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-empty': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-empty-character-class': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-empty-pattern': 'warn',

          // RATIONALE:         Eval is a security concern and a performance concern.
          'no-eval': 'warn',

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-ex-assign': 'error',

          // RATIONALE:         System types are global and should not be tampered with in a scalable code base.
          //                    If two different libraries (or two versions of the same library) both try to modify
          //                    a type, only one of them can win.  Polyfills are acceptable because they implement
          //                    a standardized interoperable contract, but polyfills are generally coded in plain
          //                    JavaScript.
          'no-extend-native': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-extra-boolean-cast': 'warn',

          'no-extra-label': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-fallthrough': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-func-assign': 'warn',

          // RATIONALE:         Catches a common coding mistake.
          'no-implied-eval': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-invalid-regexp': 'error',

          // RATIONALE:         Catches a common coding mistake.
          'no-label-var': 'error',

          // RATIONALE:         Eliminates redundant code.
          'no-lone-blocks': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-misleading-character-class': 'error',

          // RATIONALE:         Catches a common coding mistake.
          'no-multi-str': 'error',

          // RATIONALE:         It's generally a bad practice to call "new Thing()" without assigning the result to
          //                    a variable.  Either it's part of an awkward expression like "(new Thing()).doSomething()",
          //                    or else implies that the constructor is doing nontrivial computations, which is often
          //                    a poor class design.
          'no-new': 'warn',

          // RATIONALE:         Obsolete language feature that is deprecated.
          'no-new-func': 'error',

          // RATIONALE:         Obsolete language feature that is deprecated.
          'no-new-object': 'error',

          // RATIONALE:         Obsolete notation.
          'no-new-wrappers': 'warn',

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-octal': 'error',

          // RATIONALE:         Catches code that is likely to be incorrect
          'no-octal-escape': 'error',

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-regex-spaces': 'error',

          // RATIONALE:         Catches a common coding mistake.
          'no-return-assign': 'error',

          // RATIONALE:         Security risk.
          'no-script-url': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-self-assign': 'error',

          // RATIONALE:         Catches a common coding mistake.
          'no-self-compare': 'error',

          // RATIONALE:         This avoids statements such as "while (a = next(), a && a.length);" that use
          //                    commas to create compound expressions.  In general code is more readable if each
          //                    step is split onto a separate line.  This also makes it easier to set breakpoints
          //                    in the debugger.
          'no-sequences': 'error',

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-shadow-restricted-names': 'error',

          // RATIONALE:         Obsolete language feature that is deprecated.
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-sparse-arrays': 'error',

          // RATIONALE:         Although in theory JavaScript allows any possible data type to be thrown as an exception,
          //                    such flexibility adds pointless complexity, by requiring every catch block to test
          //                    the type of the object that it receives.  Whereas if catch blocks can always assume
          //                    that their object implements the "Error" contract, then the code is simpler, and
          //                    we generally get useful additional information like a call stack.
          'no-throw-literal': 'error',

          // RATIONALE:         Catches a common coding mistake.
          'no-unmodified-loop-condition': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-unsafe-finally': 'error',

          // RATIONALE:         Catches a common coding mistake.
          'no-unused-expressions': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-unused-labels': 'warn',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-useless-catch': 'warn',

          // RATIONALE:         Avoids a potential performance problem.
          'no-useless-concat': 'warn',

          // RATIONALE:         The "var" keyword is deprecated because of its confusing "hoisting" behavior.
          //                    Always use "let" or "const" instead.
          //
          // STANDARDIZED BY:   @typescript-eslint\eslint-plugin\dist\configs\recommended.json
          'no-var': 'error',

          // RATIONALE:         Generally not needed in modern code.
          'no-void': 'error',

          // RATIONALE:         Obsolete language feature that is deprecated.
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'no-with': 'error',

          // RATIONALE:         Makes logic easier to understand, since constants always have a known value
          // @typescript-eslint\eslint-plugin\dist\configs\eslint-recommended.js
          'prefer-const': 'warn',

          // RATIONALE:         Catches a common coding mistake where "resolve" and "reject" are confused.
          'promise/param-names': 'error',

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'require-atomic-updates': 'error',

          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'require-yield': 'warn',

          // "Use strict" is redundant when using the TypeScript compiler.
          strict: ['error', 'never'],

          // RATIONALE:         Catches code that is likely to be incorrect
          // STANDARDIZED BY:   eslint\conf\eslint-recommended.js
          'use-isnan': 'error'

          // The "no-restricted-syntax" rule is a general purpose pattern matcher that we can use to experiment with
          // new rules.  If a rule works well, we should convert it to a proper rule so it gets its own name
          // for suppressions and documentation.
          // How it works:    https://eslint.org/docs/rules/no-restricted-syntax
          // AST visualizer:  https://astexplorer.net/
          // Debugger:        http://estools.github.io/esquery/
          //
          // "no-restricted-syntax": [
          // ],
        }
      },
      {
        // For unit tests, we can be a little bit less strict.  The settings below revise the
        // defaults specified above.
        files: [
          // Test files
          '*.test.ts',
          '*.test.tsx',
          '*.spec.ts',
          '*.spec.tsx',

          // Facebook convention
          '**/__mocks__/*.ts',
          '**/__mocks__/*.tsx',
          '**/__tests__/*.ts',
          '**/__tests__/*.tsx',

          // Microsoft convention
          '**/test/*.ts',
          '**/test/*.tsx'
        ],
        rules: {
          // Unit tests sometimes use a standalone statement like "new Thing(123);" to test a constructor.
          'no-new': 'off',

          // Jest's mocking API is designed in a way that produces compositional data types that often have
          // no concise description.  Since test code does not ship, and typically does not introduce new
          // concepts or algorithms, the usual arguments for prioritizing readability over writability can be
          // relaxed in this case.
          '@rushstack/typedef-var': 'off',
          '@typescript-eslint/typedef': [
            'warn',
            {
              arrayDestructuring: false,
              arrowParameter: false,
              memberVariableDeclaration: true,
              objectDestructuring: false,
              parameter: true,
              propertyDeclaration: true,
              variableDeclaration: false, // <--- special case for test files
              variableDeclarationIgnoreFunction: true
            }
          ]
        }
      }
    ]
  };
}

exports.buildRules = buildRules;
exports.namingConventionRuleOptions = namingConventionRuleOptions;
