diff --git a/packages/eslint-plugin-typescript/.prettierignore b/packages/eslint-plugin-typescript/.prettierignore index 3c3629e647f..ad964914e54 100644 --- a/packages/eslint-plugin-typescript/.prettierignore +++ b/packages/eslint-plugin-typescript/.prettierignore @@ -1 +1,3 @@ node_modules +lib/configs/recommended.json +.vscode diff --git a/packages/eslint-plugin-typescript/.vscode/launch.json b/packages/eslint-plugin-typescript/.vscode/launch.json index fddfdbf5104..5c6c8ba72f1 100644 --- a/packages/eslint-plugin-typescript/.vscode/launch.json +++ b/packages/eslint-plugin-typescript/.vscode/launch.json @@ -17,6 +17,14 @@ ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "update-recommended.js", + "args": [ + "${workspaceFolder}/tools/update-recommended.js" + ] } ] } diff --git a/packages/eslint-plugin-typescript/README.md b/packages/eslint-plugin-typescript/README.md index 599163418d6..de106865dde 100644 --- a/packages/eslint-plugin-typescript/README.md +++ b/packages/eslint-plugin-typescript/README.md @@ -26,7 +26,7 @@ $ npm install eslint-plugin-typescript --save-dev ## Usage -Add `eslint-plugin-typescript/parser` to the `parser` field and `typescript` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: +Add `eslint-plugin-typescript/parser` to the `parser` field and `typescript` to the plugins section of your `.eslintrc` configuration file: ```json { @@ -35,18 +35,28 @@ Add `eslint-plugin-typescript/parser` to the `parser` field and `typescript` to } ``` +Note: The plugin provides its own version of the `typescript-eslint-parser` via `eslint-plugin-typescript/parser`. +This helps us guarantee 100% compatibility between the plugin and the parser. + Then configure the rules you want to use under the rules section. ```json { + "parser": "eslint-plugin-typescript/parser", + "plugins": ["typescript"], "rules": { "typescript/rule-name": "error" } } ``` -Note: The plugin provides its own version of the `typescript-eslint-parser` via `eslint-plugin-typescript/parser`. -This guarantees 100% compatibility between the plugin and the parser. +You can also enable all the recommended rules at once. Add `plugin:typescript/recommended` in extends: + +```json +{ + "extends": ["plugin:typescript/recommended"] +} +``` ## Supported Rules @@ -58,38 +68,38 @@ This guarantees 100% compatibility between the plugin and the parser. | Name | Description | :heavy_check_mark: | :wrench: | | ---- | ----------- | ------------------ | -------- | -| [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | | | -| [`typescript/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | | :wrench: | -| [`typescript/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | | :wrench: | -| [`typescript/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | | | +| [`typescript/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | +| [`typescript/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | +| [`typescript/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | +| [`typescript/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | [`typescript/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | -| [`typescript/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | | | -| [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | | | +| [`typescript/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | +| [`typescript/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | | [`typescript/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | | [`typescript/indent`](./docs/rules/indent.md) | Enforce consistent indentation (`indent` from TSLint) | :heavy_check_mark: | :wrench: | -| [`typescript/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | | | -| [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | | :wrench: | +| [`typescript/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | :heavy_check_mark: | | +| [`typescript/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | | [`typescript/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | | [`typescript/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | -| [`typescript/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions (`no-angle-bracket-type-assertion` from TSLint) | | | -| [`typescript/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | | :wrench: | -| [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | | | -| [`typescript/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | | | +| [`typescript/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions (`no-angle-bracket-type-assertion` from TSLint) | :heavy_check_mark: | | +| [`typescript/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | +| [`typescript/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | :heavy_check_mark: | | +| [`typescript/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | | | [`typescript/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | -| [`typescript/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | | :wrench: | -| [`typescript/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | | | -| [`typescript/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | | | -| [`typescript/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | | | -| [`typescript/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | | | -| [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | | | +| [`typescript/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | +| [`typescript/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | +| [`typescript/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | | +| [`typescript/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | :heavy_check_mark: | | +| [`typescript/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | :heavy_check_mark: | | +| [`typescript/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | :heavy_check_mark: | | | [`typescript/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | | -| [`typescript/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | | | +| [`typescript/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | :heavy_check_mark: | | | [`typescript/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | | | [`typescript/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables (`no-unused-variable` from TSLint) | :heavy_check_mark: | | -| [`typescript/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | -| [`typescript/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | | | -| [`typescript/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | | :wrench: | -| [`typescript/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | | :wrench: | -| [`typescript/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | | :wrench: | +| [`typescript/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | +| [`typescript/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | | +| [`typescript/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | +| [`typescript/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | +| [`typescript/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | diff --git a/packages/eslint-plugin-typescript/docs/rules/ban-types.md b/packages/eslint-plugin-typescript/docs/rules/ban-types.md index 655ecaeac21..e1129954d3e 100644 --- a/packages/eslint-plugin-typescript/docs/rules/ban-types.md +++ b/packages/eslint-plugin-typescript/docs/rules/ban-types.md @@ -37,12 +37,12 @@ class Foo extends Bar implements Baz { // report usages of the type using the default error message "Foo": null, - // add a custom message to help explain why not to use it + // add a custom message to help explain why not to use it "Bar": "Don't use bar!", // add a custom message, AND tell the plugin how to fix it "String": { - "message": "Use string instead", + "message": "Use string instead", "fixWith": "string" } } diff --git a/packages/eslint-plugin-typescript/lib/configs/recommended.json b/packages/eslint-plugin-typescript/lib/configs/recommended.json index f76e1c65269..58a2568e7ef 100644 --- a/packages/eslint-plugin-typescript/lib/configs/recommended.json +++ b/packages/eslint-plugin-typescript/lib/configs/recommended.json @@ -1,7 +1,42 @@ { + "parser": "eslint-plugin-typescript/parser", + "parserOptions": { + "sourceType": "module" + }, + "plugins": [ + "typescript" + ], "rules": { - "class-name-casing": "error", - "indent": "error", - "no-unused-vars": "error" + "typescript/adjacent-overload-signatures": "error", + "typescript/array-type": "error", + "typescript/ban-types": "error", + "camelcase": "off", + "typescript/camelcase": "error", + "typescript/class-name-casing": "error", + "typescript/explicit-function-return-type": "warning", + "typescript/explicit-member-accessibility": "error", + "indent": "off", + "typescript/indent": "error", + "typescript/interface-name-prefix": "error", + "typescript/member-delimiter-style": "error", + "typescript/no-angle-bracket-type-assertion": "error", + "no-array-constructor": "off", + "typescript/no-array-constructor": "error", + "typescript/no-empty-interface": "error", + "typescript/no-explicit-any": "warning", + "typescript/no-inferrable-types": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-assertion": "error", + "typescript/no-object-literal-type-assertion": "error", + "typescript/no-parameter-properties": "error", + "typescript/no-triple-slash-reference": "error", + "no-unused-vars": "off", + "typescript/no-unused-vars": "warning", + "typescript/no-use-before-define": "error", + "typescript/no-var-requires": "error", + "typescript/prefer-interface": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/type-annotation-spacing": "error" } } diff --git a/packages/eslint-plugin-typescript/lib/rules/adjacent-overload-signatures.js b/packages/eslint-plugin-typescript/lib/rules/adjacent-overload-signatures.js index 41c9a752c70..2f4a7828d73 100644 --- a/packages/eslint-plugin-typescript/lib/rules/adjacent-overload-signatures.js +++ b/packages/eslint-plugin-typescript/lib/rules/adjacent-overload-signatures.js @@ -18,6 +18,7 @@ module.exports = { category: "TypeScript", extraDescription: [util.tslintRule("adjacent-overload-signatures")], url: util.metaDocsUrl("adjacent-overload-signatures"), + recommended: "error", }, schema: [], messages: { diff --git a/packages/eslint-plugin-typescript/lib/rules/array-type.js b/packages/eslint-plugin-typescript/lib/rules/array-type.js index e7f9c1ce1f7..ceeec210ffa 100644 --- a/packages/eslint-plugin-typescript/lib/rules/array-type.js +++ b/packages/eslint-plugin-typescript/lib/rules/array-type.js @@ -78,6 +78,8 @@ function typeNeedsParentheses(node) { // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = ["array"]; + module.exports = { meta: { type: "suggestion", @@ -86,6 +88,7 @@ module.exports = { extraDescription: [util.tslintRule("array-type")], category: "TypeScript", url: util.metaDocsUrl("array-type"), + recommended: "error", }, fixable: "code", messages: { @@ -105,7 +108,7 @@ module.exports = { ], }, create(context) { - const option = context.options[0] || "array"; + const option = util.applyDefault(defaultOptions, context.options)[0]; const sourceCode = context.getSourceCode(); /** diff --git a/packages/eslint-plugin-typescript/lib/rules/ban-types.js b/packages/eslint-plugin-typescript/lib/rules/ban-types.js index 9027a5b21c3..eba560e97bc 100644 --- a/packages/eslint-plugin-typescript/lib/rules/ban-types.js +++ b/packages/eslint-plugin-typescript/lib/rules/ban-types.js @@ -10,6 +10,33 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + types: { + String: { + message: "Use string instead", + fixWith: "string", + }, + Boolean: { + message: "Use boolean instead", + fixWith: "boolean", + }, + Number: { + message: "Use number instead", + fixWith: "number", + }, + Object: { + message: "Use Record instead", + fixWith: "Record", + }, + Symbol: { + message: "Use symbol instead", + fixWith: "symbol", + }, + }, + }, +]; + module.exports = { meta: { type: "suggestion", @@ -18,6 +45,7 @@ module.exports = { extraDescription: [util.tslintRule("ban-types")], category: "TypeScript", url: util.metaDocsUrl("ban-types"), + recommended: "error", }, fixable: "code", messages: { @@ -52,7 +80,8 @@ module.exports = { }, create(context) { - const banedTypes = (context.options[0] || {}).types || {}; + const banedTypes = util.applyDefault(defaultOptions, context.options)[0] + .types; //---------------------------------------------------------------------- // Public diff --git a/packages/eslint-plugin-typescript/lib/rules/camelcase.js b/packages/eslint-plugin-typescript/lib/rules/camelcase.js index 1b9f1075fbb..c8c952eea65 100644 --- a/packages/eslint-plugin-typescript/lib/rules/camelcase.js +++ b/packages/eslint-plugin-typescript/lib/rules/camelcase.js @@ -10,6 +10,13 @@ const util = require("../util"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allow: ["^UNSAFE_"], + ignoreDestructuring: false, + properties: "never", + }, +]; /* eslint-disable eslint-plugin/require-meta-type */ module.exports = { @@ -17,6 +24,7 @@ module.exports = { docs: { description: "Enforce camelCase naming convention", url: util.metaDocsUrl("ban-types"), + recommended: "error", }, }), @@ -29,13 +37,9 @@ module.exports = { "TSAbstractClassProperty", ]; - const options = context.options[0] || {}; - let properties = options.properties || ""; - const allow = options.allow || []; - - if (properties !== "always" && properties !== "never") { - properties = "always"; - } + const options = util.applyDefault(defaultOptions, context.options)[0]; + const properties = options.properties; + const allow = options.allow; /** * Checks if a string contains an underscore and isn't all upper-case diff --git a/packages/eslint-plugin-typescript/lib/rules/class-name-casing.js b/packages/eslint-plugin-typescript/lib/rules/class-name-casing.js index 0ff94a28f64..92861080088 100644 --- a/packages/eslint-plugin-typescript/lib/rules/class-name-casing.js +++ b/packages/eslint-plugin-typescript/lib/rules/class-name-casing.js @@ -18,9 +18,10 @@ module.exports = { description: "Require PascalCased class and interface names", extraDescription: [util.tslintRule("class-name")], category: "Best Practices", - recommended: true, url: util.metaDocsUrl("class-name-casing"), + recommended: "error", }, + schema: [], }, create(context) { diff --git a/packages/eslint-plugin-typescript/lib/rules/explicit-function-return-type.js b/packages/eslint-plugin-typescript/lib/rules/explicit-function-return-type.js index 73b255a45c0..f3402af23b2 100644 --- a/packages/eslint-plugin-typescript/lib/rules/explicit-function-return-type.js +++ b/packages/eslint-plugin-typescript/lib/rules/explicit-function-return-type.js @@ -10,6 +10,12 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allowExpressions: true, + }, +]; + module.exports = { meta: { type: "problem", @@ -18,6 +24,7 @@ module.exports = { "Require explicit return types on functions and class methods", category: "TypeScript", url: util.metaDocsUrl("explicit-function-return-type"), + recommended: "warning", }, schema: [ { @@ -33,7 +40,7 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; + const options = util.applyDefault(defaultOptions, context.options)[0]; //---------------------------------------------------------------------- // Helpers @@ -69,14 +76,6 @@ module.exports = { * @private */ function checkFunctionReturnType(node) { - if ( - options.allowExpressions && - node.type !== "FunctionDeclaration" && - node.parent.type !== "VariableDeclarator" - ) { - return; - } - if ( !node.returnType && !isConstructor(node.parent) && @@ -90,13 +89,31 @@ module.exports = { } } + /** + * Checks if a function declaration/expression has a return type. + * @param {ASTNode} node The node representing a function. + * @returns {void} + * @private + */ + function checkFunctionExpressionReturnType(node) { + if ( + options.allowExpressions && + node.parent.type !== "VariableDeclarator" && + node.parent.type !== "MethodDefinition" + ) { + return; + } + + checkFunctionReturnType(node); + } + //---------------------------------------------------------------------- // Public //---------------------------------------------------------------------- return { FunctionDeclaration: checkFunctionReturnType, - FunctionExpression: checkFunctionReturnType, - ArrowFunctionExpression: checkFunctionReturnType, + FunctionExpression: checkFunctionExpressionReturnType, + ArrowFunctionExpression: checkFunctionExpressionReturnType, }; }, }; diff --git a/packages/eslint-plugin-typescript/lib/rules/explicit-member-accessibility.js b/packages/eslint-plugin-typescript/lib/rules/explicit-member-accessibility.js index 27f98bde1a6..cf1da4cf50b 100644 --- a/packages/eslint-plugin-typescript/lib/rules/explicit-member-accessibility.js +++ b/packages/eslint-plugin-typescript/lib/rules/explicit-member-accessibility.js @@ -19,6 +19,7 @@ module.exports = { extraDescription: [util.tslintRule("member-access")], category: "TypeScript", url: util.metaDocsUrl("explicit-member-accessibility"), + recommended: "error", }, schema: [], }, diff --git a/packages/eslint-plugin-typescript/lib/rules/generic-type-naming.js b/packages/eslint-plugin-typescript/lib/rules/generic-type-naming.js index 0e00c85868a..4e3f4214e7d 100644 --- a/packages/eslint-plugin-typescript/lib/rules/generic-type-naming.js +++ b/packages/eslint-plugin-typescript/lib/rules/generic-type-naming.js @@ -40,6 +40,11 @@ function createTypeParameterChecker(context, rule) { }; } +const defaultOptions = [ + // Matches: T , TA , TAbc , TA1Bca , T1 , T2 + "^T([A-Z0-9][a-zA-Z0-9]*){0,1}$", +]; + module.exports = { meta: { type: "suggestion", @@ -52,15 +57,16 @@ module.exports = { paramNotMatchRule: "Type parameter {{name}} does not match rule {{rule}}.", }, + schema: [ + { + type: "string", + }, + ], + recommended: "error", }, create(context) { - const rule = context.options[0]; - - if (!rule) { - return {}; - } - + const rule = util.applyDefault(defaultOptions, context.options)[0]; const checkTypeParameters = createTypeParameterChecker(context, rule); return { diff --git a/packages/eslint-plugin-typescript/lib/rules/indent.js b/packages/eslint-plugin-typescript/lib/rules/indent.js index 887a053259f..4b757af8634 100644 --- a/packages/eslint-plugin-typescript/lib/rules/indent.js +++ b/packages/eslint-plugin-typescript/lib/rules/indent.js @@ -33,6 +33,33 @@ const KNOWN_NODES = new Set([ "TSTypeLiteral", ]); +const defaultOptions = [ + // typescript docs and playground use 4 space indent + 4, + { + // typescript docs indent the case from the switch + // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-8.html#example-4 + SwitchCase: 1, + flatTernaryExpressions: false, + // list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js + ignoredNodes: [ + "JSXElement", + "JSXElement > *", + "JSXAttribute", + "JSXIdentifier", + "JSXNamespacedName", + "JSXMemberExpression", + "JSXSpreadAttribute", + "JSXExpressionContainer", + "JSXOpeningElement", + "JSXClosingElement", + "JSXText", + "JSXEmptyExpression", + "JSXSpreadChild", + ], + }, +]; + module.exports = Object.assign({}, baseRule, { meta: { type: "layout", @@ -40,14 +67,25 @@ module.exports = Object.assign({}, baseRule, { description: "Enforce consistent indentation", extraDescription: [util.tslintRule("indent")], category: "Stylistic Issues", - recommended: true, + recommended: "error", url: util.metaDocsUrl("indent"), }, fixable: "whitespace", + schema: baseRule.meta.schema, }, create(context) { - const rules = baseRule.create(context); + // because we extend the base rule, have to update opts on the context + // the context defines options as readonly though... + const contextWithDefaults = Object.create(context, { + options: { + writable: false, + configurable: false, + value: util.applyDefault(defaultOptions, context.options), + }, + }); + + const rules = baseRule.create(contextWithDefaults); /** * Converts from a TSPropertySignature to a Property diff --git a/packages/eslint-plugin-typescript/lib/rules/interface-name-prefix.js b/packages/eslint-plugin-typescript/lib/rules/interface-name-prefix.js index 29ed72142c7..36ea5fb6e79 100644 --- a/packages/eslint-plugin-typescript/lib/rules/interface-name-prefix.js +++ b/packages/eslint-plugin-typescript/lib/rules/interface-name-prefix.js @@ -10,6 +10,8 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = ["never"]; + module.exports = { meta: { type: "suggestion", @@ -18,6 +20,7 @@ module.exports = { extraDescription: [util.tslintRule("interface-name")], category: "TypeScript", url: util.metaDocsUrl("interface-name-prefix"), + recommended: "error", }, schema: [ { @@ -27,7 +30,8 @@ module.exports = { }, create(context) { - const never = context.options[0] !== "always"; + const option = util.applyDefault(defaultOptions, context.options)[0]; + const never = option !== "always"; //---------------------------------------------------------------------- // Helpers diff --git a/packages/eslint-plugin-typescript/lib/rules/member-delimiter-style.js b/packages/eslint-plugin-typescript/lib/rules/member-delimiter-style.js index cea76fca3e8..944350ff0e7 100644 --- a/packages/eslint-plugin-typescript/lib/rules/member-delimiter-style.js +++ b/packages/eslint-plugin-typescript/lib/rules/member-delimiter-style.js @@ -5,12 +5,25 @@ */ "use strict"; -const { deepMerge, metaDocsUrl } = require("../util"); +const { deepMerge, metaDocsUrl, applyDefault } = require("../util"); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + multiline: { + delimiter: "semi", + requireLast: true, + }, + singleline: { + delimiter: "semi", + requireLast: false, + }, + }, +]; + const definition = { type: "object", properties: { @@ -43,6 +56,7 @@ module.exports = { "Require a specific member delimiter style for interfaces and type literals", category: "TypeScript", url: metaDocsUrl("member-delimiter-style"), + recommended: "error", }, fixable: "code", messages: { @@ -71,21 +85,11 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); - const options = context.options[0] || {}; - - const overrides = options.overrides || {}; - const defaults = { - multiline: { - delimiter: "semi", - requireLast: true, - }, - singleline: { - delimiter: "semi", - requireLast: false, - }, - }; + const options = applyDefault(defaultOptions, context.options)[0]; - const baseOptions = deepMerge(defaults, options); + // use the base options as the defaults for the cases + const baseOptions = options; + const overrides = baseOptions.overrides || {}; const interfaceOptions = deepMerge(baseOptions, overrides.interface); const typeLiteralOptions = deepMerge( baseOptions, diff --git a/packages/eslint-plugin-typescript/lib/rules/member-naming.js b/packages/eslint-plugin-typescript/lib/rules/member-naming.js index 62fb3396a02..2b1427ff07d 100644 --- a/packages/eslint-plugin-typescript/lib/rules/member-naming.js +++ b/packages/eslint-plugin-typescript/lib/rules/member-naming.js @@ -10,6 +10,8 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [{}]; + module.exports = { meta: { type: "suggestion", @@ -18,27 +20,41 @@ module.exports = { "Enforces naming conventions for class members by visibility.", category: "TypeScript", url: util.metaDocsUrl("member-naming"), + recommended: false, }, schema: [ { type: "object", properties: { - public: { type: "string" }, - protected: { type: "string" }, - private: { type: "string" }, + public: { + type: "string", + minLength: 1, + format: "regex", + }, + protected: { + type: "string", + minLength: 1, + format: "regex", + }, + private: { + type: "string", + minLength: 1, + format: "regex", + }, }, additionalProperties: false, + minProperties: 1, }, ], }, create(context) { - const config = context.options[0] || {}; - const conventions = {}; + const config = util.applyDefault(defaultOptions, context.options)[0]; + const conventions = Object.keys(config).reduce((acc, accessibility) => { + acc[accessibility] = new RegExp(config[accessibility]); - for (const accessibility of Object.getOwnPropertyNames(config)) { - conventions[accessibility] = new RegExp(config[accessibility]); - } + return acc; + }, {}); //---------------------------------------------------------------------- // Helpers diff --git a/packages/eslint-plugin-typescript/lib/rules/member-ordering.js b/packages/eslint-plugin-typescript/lib/rules/member-ordering.js index 00dea4962ae..24a804545f3 100644 --- a/packages/eslint-plugin-typescript/lib/rules/member-ordering.js +++ b/packages/eslint-plugin-typescript/lib/rules/member-ordering.js @@ -31,6 +31,48 @@ const schemaOptions = ["field", "method", "constructor"].reduce( [] ); +const defaultOptions = [ + { + default: [ + "public-static-field", + "protected-static-field", + "private-static-field", + + "public-instance-field", + "protected-instance-field", + "private-instance-field", + + "public-field", + "protected-field", + "private-field", + + "static-field", + "instance-field", + + "field", + + "constructor", + + "public-static-method", + "protected-static-method", + "private-static-method", + + "public-instance-method", + "protected-instance-method", + "private-instance-method", + + "public-method", + "protected-method", + "private-method", + + "static-method", + "instance-method", + + "method", + ], + }, +]; + module.exports = { meta: { type: "suggestion", @@ -39,6 +81,7 @@ module.exports = { extraDescription: [util.tslintRule("member-ordering")], category: "TypeScript", url: util.metaDocsUrl("member-ordering"), + recommended: false, }, schema: [ { @@ -116,49 +159,12 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; + const options = util.applyDefault(defaultOptions, context.options)[0]; const functionExpressions = [ "FunctionExpression", "ArrowFunctionExpression", ]; - const defaultOrder = [ - "public-static-field", - "protected-static-field", - "private-static-field", - - "public-instance-field", - "protected-instance-field", - "private-instance-field", - - "public-field", - "protected-field", - "private-field", - - "static-field", - "instance-field", - - "field", - - "constructor", - - "public-static-method", - "protected-static-method", - "private-static-method", - - "public-instance-method", - "protected-instance-method", - "private-instance-method", - - "public-method", - "protected-method", - "private-method", - - "static-method", - "instance-method", - - "method", - ]; //---------------------------------------------------------------------- // Helpers @@ -350,28 +356,28 @@ module.exports = { ClassDeclaration(node) { validateMembers( node.body.body, - options.classes || options.default || defaultOrder, + options.classes || options.default, true ); }, ClassExpression(node) { validateMembers( node.body.body, - options.classExpressions || options.default || defaultOrder, + options.classExpressions || options.default, true ); }, TSInterfaceDeclaration(node) { validateMembers( node.body.body, - options.interfaces || options.default || defaultOrder, + options.interfaces || options.default, false ); }, TSTypeLiteral(node) { validateMembers( node.members, - options.typeLiterals || options.default || defaultOrder, + options.typeLiterals || options.default, false ); }, diff --git a/packages/eslint-plugin-typescript/lib/rules/no-angle-bracket-type-assertion.js b/packages/eslint-plugin-typescript/lib/rules/no-angle-bracket-type-assertion.js index f46429291fd..732c2641546 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-angle-bracket-type-assertion.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-angle-bracket-type-assertion.js @@ -21,6 +21,7 @@ module.exports = { ], category: "Style", url: util.metaDocsUrl("no-angle-bracket-type-assertion"), + recommended: "error", }, schema: [], }, diff --git a/packages/eslint-plugin-typescript/lib/rules/no-array-constructor.js b/packages/eslint-plugin-typescript/lib/rules/no-array-constructor.js index ca8b0e2fa94..6576c8958a7 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-array-constructor.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-array-constructor.js @@ -17,8 +17,8 @@ module.exports = { docs: { description: "Disallow generic `Array` constructors", category: "Stylistic Issues", - recommended: false, url: util.metaDocsUrl("no-array-constructor"), + recommended: "error", }, fixable: "code", schema: [], diff --git a/packages/eslint-plugin-typescript/lib/rules/no-empty-interface.js b/packages/eslint-plugin-typescript/lib/rules/no-empty-interface.js index 5640aaf5e9d..8cc30e81c9c 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-empty-interface.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-empty-interface.js @@ -18,6 +18,7 @@ module.exports = { extraDescription: [util.tslintRule("no-empty-interface")], category: "TypeScript", url: util.metaDocsUrl("no-empty-interface"), + recommended: "error", }, schema: [], }, diff --git a/packages/eslint-plugin-typescript/lib/rules/no-explicit-any.js b/packages/eslint-plugin-typescript/lib/rules/no-explicit-any.js index 677f1dc506f..fa7f6b624b7 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-explicit-any.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-explicit-any.js @@ -19,6 +19,7 @@ module.exports = { extraDescription: [util.tslintRule("no-any")], category: "TypeScript", url: util.metaDocsUrl("no-explicit-any"), + recommended: "warning", }, schema: [], }, diff --git a/packages/eslint-plugin-typescript/lib/rules/no-extraneous-class.js b/packages/eslint-plugin-typescript/lib/rules/no-extraneous-class.js index 1f4b9173b2c..5dbc06e8aac 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-extraneous-class.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-extraneous-class.js @@ -10,6 +10,14 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allowConstructorOnly: false, + allowEmpty: false, + allowStaticOnly: false, + }, +]; + module.exports = { meta: { type: "suggestion", @@ -17,8 +25,8 @@ module.exports = { description: "Forbids the use of classes as namespaces", extraDescription: [util.tslintRule("no-unnecessary-class")], category: "Best Practices", - recommended: false, url: util.metaDocsUrl("no-extraneous-class"), + recommended: false, }, fixable: null, schema: [ @@ -46,8 +54,11 @@ module.exports = { }, create(context) { - const { allowConstructorOnly, allowEmpty, allowStaticOnly } = - context.options[0] || {}; + const { + allowConstructorOnly, + allowEmpty, + allowStaticOnly, + } = util.applyDefault(defaultOptions, context.options)[0]; return { ClassBody(node) { diff --git a/packages/eslint-plugin-typescript/lib/rules/no-inferrable-types.js b/packages/eslint-plugin-typescript/lib/rules/no-inferrable-types.js index ed0bd88d5a0..e78b5741b8b 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-inferrable-types.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-inferrable-types.js @@ -10,6 +10,13 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + ignoreParameters: true, + ignoreProperties: true, + }, +]; + module.exports = { meta: { type: "suggestion", @@ -19,6 +26,7 @@ module.exports = { extraDescription: [util.tslintRule("no-inferrable-types")], category: "TypeScript", url: util.metaDocsUrl("no-inferrable-types"), + recommended: "error", }, fixable: "code", schema: [ @@ -38,12 +46,10 @@ module.exports = { }, create(context) { - const ignoreParameters = context.options[0] - ? context.options[0].ignoreParameters - : false; - const ignoreProperties = context.options[0] - ? context.options[0].ignoreProperties - : false; + const { ignoreParameters, ignoreProperties } = util.applyDefault( + defaultOptions, + context.options + )[0]; /** * Returns whether a node has an inferrable value or not diff --git a/packages/eslint-plugin-typescript/lib/rules/no-misused-new.js b/packages/eslint-plugin-typescript/lib/rules/no-misused-new.js index 41a17c0adbc..d18a8f7d715 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-misused-new.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-misused-new.js @@ -18,6 +18,7 @@ module.exports = { extraDescription: [util.tslintRule("no-misused-new")], category: "TypeScript", url: util.metaDocsUrl("no-misused-new"), + recommended: "error", }, schema: [], messages: { diff --git a/packages/eslint-plugin-typescript/lib/rules/no-namespace.js b/packages/eslint-plugin-typescript/lib/rules/no-namespace.js index 59853b6d2b5..ff3d2b2dd07 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-namespace.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-namespace.js @@ -10,6 +10,13 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allowDeclarations: false, + allowDefinitionFiles: true, + }, +]; + module.exports = { meta: { type: "suggestion", @@ -19,6 +26,7 @@ module.exports = { extraDescription: [util.tslintRule("no-namespace")], category: "TypeScript", url: util.metaDocsUrl("no-namespace"), + recommended: "error", }, messages: { moduleSyntaxIsPreferred: @@ -41,9 +49,10 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const allowDeclarations = options.allowDeclarations || false; - const allowDefinitionFiles = options.allowDefinitionFiles || false; + const { allowDeclarations, allowDefinitionFiles } = util.applyDefault( + defaultOptions, + context.options + )[0]; const filename = context.getFilename(); //---------------------------------------------------------------------- diff --git a/packages/eslint-plugin-typescript/lib/rules/no-non-null-assertion.js b/packages/eslint-plugin-typescript/lib/rules/no-non-null-assertion.js index e4e11cf9d52..0b961e990e3 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-non-null-assertion.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-non-null-assertion.js @@ -19,6 +19,7 @@ module.exports = { extraDescription: [util.tslintRule("no-non-null-assertion")], category: "TypeScript", url: util.metaDocsUrl("no-non-null-assertion"), + recommended: "error", }, schema: [], }, diff --git a/packages/eslint-plugin-typescript/lib/rules/no-object-literal-type-assertion.js b/packages/eslint-plugin-typescript/lib/rules/no-object-literal-type-assertion.js index 02751c48458..468ff807b42 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-object-literal-type-assertion.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-object-literal-type-assertion.js @@ -21,6 +21,7 @@ module.exports = { ], category: "TypeScript", url: util.metaDocsUrl("no-object-literal-type-assertions"), + recommended: "error", }, messages: { unexpectedTypeAssertion: diff --git a/packages/eslint-plugin-typescript/lib/rules/no-parameter-properties.js b/packages/eslint-plugin-typescript/lib/rules/no-parameter-properties.js index 648864c03b3..593df1cb20f 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-parameter-properties.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-parameter-properties.js @@ -10,6 +10,12 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allows: [], + }, +]; + module.exports = { meta: { type: "problem", @@ -19,6 +25,7 @@ module.exports = { extraDescription: [util.tslintRule("no-parameter-properties")], category: "TypeScript", url: util.metaDocsUrl("no-parameter-properties"), + recommended: "error", }, schema: [ { @@ -46,8 +53,10 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - const allows = options.allows || []; + const { allows } = util.applyDefault( + defaultOptions, + context.options + )[0]; //---------------------------------------------------------------------- // Helpers diff --git a/packages/eslint-plugin-typescript/lib/rules/no-this-alias.js b/packages/eslint-plugin-typescript/lib/rules/no-this-alias.js index bf74e3fadb3..a32f4720591 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-this-alias.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-this-alias.js @@ -10,6 +10,13 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allowDestructuring: false, + allowedNames: [], + }, +]; + module.exports = { meta: { type: "suggestion", @@ -17,8 +24,8 @@ module.exports = { description: "Disallow aliasing `this`", extraDescription: [util.tslintRule("no-this-assignment")], category: "Best Practices", - recommended: false, url: util.metaDocsUrl("no-this-alias"), + recommended: false, }, fixable: null, schema: [ @@ -46,8 +53,10 @@ module.exports = { }, create(context) { - const { allowDestructuring = false, allowedNames = [] } = - context.options[0] || {}; + const { allowDestructuring, allowedNames } = util.applyDefault( + defaultOptions, + context.options + )[0]; return { VariableDeclarator(node) { diff --git a/packages/eslint-plugin-typescript/lib/rules/no-triple-slash-reference.js b/packages/eslint-plugin-typescript/lib/rules/no-triple-slash-reference.js index 7410df68a39..4eaf1d260cf 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-triple-slash-reference.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-triple-slash-reference.js @@ -18,6 +18,7 @@ module.exports = { extraDescription: [util.tslintRule("no-reference")], category: "TypeScript", url: util.metaDocsUrl("no-triple-slash-reference"), + recommended: "error", }, schema: [], messages: { diff --git a/packages/eslint-plugin-typescript/lib/rules/no-type-alias.js b/packages/eslint-plugin-typescript/lib/rules/no-type-alias.js index 1778ad266ca..b6d8851bca1 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-type-alias.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-type-alias.js @@ -10,6 +10,15 @@ const util = require("../util"); // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + allowAliases: "never", + allowCallbacks: "never", + allowLiterals: "never", + allowMappedTypes: "never", + }, +]; + module.exports = { meta: { type: "suggestion", @@ -18,6 +27,7 @@ module.exports = { extraDescription: [util.tslintRule("interface-over-type-literal")], category: "TypeScript", url: util.metaDocsUrl("no-type-alias"), + recommended: false, }, messages: { noTypeAlias: "Type {{alias}} are not allowed.", @@ -65,12 +75,12 @@ module.exports = { }, create(context) { - const options = context.options[0] || {}; - - const allowAliases = options.allowAliases || "never"; - const allowCallbacks = options.allowCallbacks || "never"; - const allowLiterals = options.allowLiterals || "never"; - const allowMappedTypes = options.allowMappedTypes || "never"; + const { + allowAliases, + allowCallbacks, + allowLiterals, + allowMappedTypes, + } = util.applyDefault(defaultOptions, context.options)[0]; const unions = ["always", "in-unions", "in-unions-and-intersections"]; const intersections = [ diff --git a/packages/eslint-plugin-typescript/lib/rules/no-unused-vars.js b/packages/eslint-plugin-typescript/lib/rules/no-unused-vars.js index 1ddea3a8083..a165fed4702 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-unused-vars.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-unused-vars.js @@ -19,7 +19,7 @@ module.exports = Object.assign({}, baseRule, { extraDescription: [util.tslintRule("no-unused-variable")], category: "Variables", url: util.metaDocsUrl("no-unused-vars"), - recommended: true, + recommended: "warning", }, schema: baseRule.meta.schema, }, diff --git a/packages/eslint-plugin-typescript/lib/rules/no-use-before-define.js b/packages/eslint-plugin-typescript/lib/rules/no-use-before-define.js index e5e47d62d86..0216f743381 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-use-before-define.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-use-before-define.js @@ -178,6 +178,15 @@ function isInInitializer(variable, reference) { // Rule Definition //------------------------------------------------------------------------------ +const defaultOptions = [ + { + functions: true, + classes: true, + variables: true, + typedefs: true, + }, +]; + module.exports = { meta: { type: "problem", @@ -185,8 +194,8 @@ module.exports = { description: "Disallow the use of variables before they are defined", category: "Variables", - recommended: false, url: util.metaDocsUrl("no-use-before-define"), + recommended: "error", }, schema: [ { @@ -210,7 +219,9 @@ module.exports = { }, create(context) { - const options = parseOptions(context.options[0]); + const options = parseOptions( + util.applyDefault(defaultOptions, context.options)[0] + ); /** * Determines whether a given use-before-define case should be reported according to the options. diff --git a/packages/eslint-plugin-typescript/lib/rules/no-var-requires.js b/packages/eslint-plugin-typescript/lib/rules/no-var-requires.js index b8ab589fd03..328dc796123 100644 --- a/packages/eslint-plugin-typescript/lib/rules/no-var-requires.js +++ b/packages/eslint-plugin-typescript/lib/rules/no-var-requires.js @@ -19,6 +19,7 @@ module.exports = { extraDescription: [util.tslintRule("no-var-requires")], category: "TypeScript", url: util.metaDocsUrl("no-var-requires"), + recommended: "error", }, schema: [], }, diff --git a/packages/eslint-plugin-typescript/lib/rules/prefer-interface.js b/packages/eslint-plugin-typescript/lib/rules/prefer-interface.js index 8694e3aa95b..da090bc2af7 100644 --- a/packages/eslint-plugin-typescript/lib/rules/prefer-interface.js +++ b/packages/eslint-plugin-typescript/lib/rules/prefer-interface.js @@ -19,6 +19,7 @@ module.exports = { extraDescription: [util.tslintRule("interface-over-type-literal")], category: "TypeScript", url: util.metaDocsUrl("prefer-interface"), + recommended: "error", }, fixable: "code", messages: { diff --git a/packages/eslint-plugin-typescript/lib/rules/prefer-namespace-keyword.js b/packages/eslint-plugin-typescript/lib/rules/prefer-namespace-keyword.js index cffd13f7b39..76bb6545c64 100644 --- a/packages/eslint-plugin-typescript/lib/rules/prefer-namespace-keyword.js +++ b/packages/eslint-plugin-typescript/lib/rules/prefer-namespace-keyword.js @@ -20,6 +20,7 @@ module.exports = { extraDescription: [util.tslintRule("no-internal-module")], category: "TypeScript", url: util.metaDocsUrl("prefer-namespace-keyword"), + recommended: "error", }, fixable: "code", schema: [], diff --git a/packages/eslint-plugin-typescript/lib/rules/type-annotation-spacing.js b/packages/eslint-plugin-typescript/lib/rules/type-annotation-spacing.js index a833b6ed192..922f2aad0b8 100644 --- a/packages/eslint-plugin-typescript/lib/rules/type-annotation-spacing.js +++ b/packages/eslint-plugin-typescript/lib/rules/type-annotation-spacing.js @@ -20,6 +20,12 @@ const definition = { additionalProperties: false, }; +const defaultOptions = [ + // technically there is a default, but the overrides mean + // that if we apply them here, it will break the no override case. + {}, +]; + module.exports = { meta: { type: "layout", @@ -28,6 +34,7 @@ module.exports = { extraDescription: [util.tslintRule("typedef-whitespace")], category: "TypeScript", url: util.metaDocsUrl("type-annotation-spacing"), + recommended: "error", }, fixable: "whitespace", schema: [ @@ -52,7 +59,7 @@ module.exports = { create(context) { const punctuators = [":", "=>"]; const sourceCode = context.getSourceCode(); - const options = context.options[0] || {}; + const options = util.applyDefault(defaultOptions, context.options)[0]; const overrides = options.overrides || {}; diff --git a/packages/eslint-plugin-typescript/lib/util.js b/packages/eslint-plugin-typescript/lib/util.js index 1dd2901191f..facacc73bc8 100644 --- a/packages/eslint-plugin-typescript/lib/util.js +++ b/packages/eslint-plugin-typescript/lib/util.js @@ -23,6 +23,15 @@ exports.isTypescript = fileName => /\.tsx?$/i.test(fileName || ""); */ exports.isDefinitionFile = fileName => /\.d\.tsx?$/i.test(fileName || ""); +/** + * Check if the variable contains an object stricly rejecting arrays + * @param {any} obj an object + * @returns {boolean} `true` if obj is an object + */ +function isObjectNotArray(obj) { + return typeof obj === "object" && !Array.isArray(obj); +} + /** * Pure function - doesn't mutate either parameter! * Merges two objects together deeply, overwriting the properties in first with the properties in second @@ -40,12 +49,7 @@ function deepMerge(first = {}, second = {}) { const secondHasKey = key in second; if (firstHasKey && secondHasKey) { - if ( - typeof first[key] === "object" && - !Array.isArray(first[key]) && - typeof second[key] === "object" && - !Array.isArray(second[key]) - ) { + if (isObjectNotArray(first[key]) && isObjectNotArray(second[key])) { // object type acc[key] = deepMerge(first[key], second[key]); } else { @@ -63,6 +67,39 @@ function deepMerge(first = {}, second = {}) { } exports.deepMerge = deepMerge; +/** + * Pure function - doesn't mutate either parameter! + * Uses the default options and overrides with the options provided by the user + * @template TOptions + * @param {TOptions} defaultOptions the defaults + * @param {any[]} userOptions the user opts + * @returns {TOptions} the options with defaults + */ +function applyDefault(defaultOptions, userOptions) { + // clone defaults + const options = JSON.parse(JSON.stringify(defaultOptions)); + + // eslint-disable-next-line eqeqeq + if (userOptions == null) { + return options; + } + + options.forEach((opt, i) => { + if (userOptions[i]) { + const userOpt = userOptions[i]; + + if (isObjectNotArray(userOpt) && isObjectNotArray(opt)) { + options[i] = deepMerge(opt, userOpt); + } else { + options[i] = userOpt; + } + } + }); + + return options; +} +exports.applyDefault = applyDefault; + /** * Upper cases the first character or the string * @param {string} str a string diff --git a/packages/eslint-plugin-typescript/package.json b/packages/eslint-plugin-typescript/package.json index 1c215739a94..342b8c6b2fa 100644 --- a/packages/eslint-plugin-typescript/package.json +++ b/packages/eslint-plugin-typescript/package.json @@ -22,7 +22,8 @@ "format": "yarn format-no-write --write && yarn prettier --write", "format-check": "yarn format-no-write --list-different && yarn prettier --list-different", "test": "mocha tests --recursive --reporter=dot", - "recommended:update": "node tools/update-recommended.js" + "recommended:update": "node tools/update-recommended.js", + "pre-commit": "yarn check --verify-tree && yarn lint && yarn format-check && yarn test && yarn docs:check" }, "dependencies": { "requireindex": "^1.2.0", @@ -53,7 +54,7 @@ }, "husky": { "hooks": { - "pre-commit": "yarn check --verify-tree && yarn lint && yarn format-check && yarn test && yarn docs:check && lint-staged" + "pre-commit": "yarn pre-commit && lint-staged" } }, "engines": { diff --git a/packages/eslint-plugin-typescript/tests/lib/rules/generic-type-naming.js b/packages/eslint-plugin-typescript/tests/lib/rules/generic-type-naming.js index 392042f3df7..f65099e935d 100644 --- a/packages/eslint-plugin-typescript/tests/lib/rules/generic-type-naming.js +++ b/packages/eslint-plugin-typescript/tests/lib/rules/generic-type-naming.js @@ -17,9 +17,9 @@ const ruleTester = new RuleTester({ ruleTester.run("generic-type-naming", rule, { valid: [ - { code: "class { }", options: [] }, + { code: "class { }", options: [] }, { code: "type ReadOnly = {}", options: [] }, - { code: "interface SimpleMap { }", options: [] }, + { code: "interface SimpleMap { }", options: [] }, { code: "function get() {}", options: [] }, { code: "interface GenericIdentityFn { (arg: T): T }", options: [] }, { code: "class { }", options: ["^x+$"] }, @@ -29,6 +29,20 @@ ruleTester.run("generic-type-naming", rule, { }, ], invalid: [ + { + code: "class { }", + options: [], + errors: [ + { + messageId: "paramNotMatchRule", + data: { name: "U", rule: "^T([A-Z0-9][a-zA-Z0-9]*){0,1}$" }, + }, + { + messageId: "paramNotMatchRule", + data: { name: "V", rule: "^T([A-Z0-9][a-zA-Z0-9]*){0,1}$" }, + }, + ], + }, { code: "class { }", options: ["^[A-Z]+$"], diff --git a/packages/eslint-plugin-typescript/tests/lib/rules/no-inferrable-types.js b/packages/eslint-plugin-typescript/tests/lib/rules/no-inferrable-types.js index 1150de3a1a9..71a5a6f0f75 100644 --- a/packages/eslint-plugin-typescript/tests/lib/rules/no-inferrable-types.js +++ b/packages/eslint-plugin-typescript/tests/lib/rules/no-inferrable-types.js @@ -111,6 +111,12 @@ ruleTester.run("no-inferrable-types", rule, { code: "const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}", output: "const fn = (a = 5, b = true, c = 'foo') => {}", + options: [ + { + ignoreParameters: false, + ignoreProperties: false, + }, + ], errors: [ { message: @@ -136,6 +142,12 @@ ruleTester.run("no-inferrable-types", rule, { code: "class Foo { a: number = 5; b: boolean = true; c: string = 'foo'; }", output: "class Foo { a = 5; b = true; c = 'foo'; }", + options: [ + { + ignoreParameters: false, + ignoreProperties: false, + }, + ], errors: [ { message: diff --git a/packages/eslint-plugin-typescript/tests/lib/util.js b/packages/eslint-plugin-typescript/tests/lib/util.js index d34f04d8759..62e3fa03d0a 100644 --- a/packages/eslint-plugin-typescript/tests/lib/util.js +++ b/packages/eslint-plugin-typescript/tests/lib/util.js @@ -4,49 +4,185 @@ const assert = require("assert"); const util = require("../../lib/util"); describe("isTypescript", () => { - it("should return false", () => { - assert.strictEqual(util.isTypescript("test.js"), false); - assert.strictEqual(util.isTypescript("test.jsx"), false); - assert.strictEqual(util.isTypescript("README.md"), false); - assert.strictEqual(util.isTypescript("test.d.js"), false); - assert.strictEqual(util.isTypescript("test.ts.js"), false); - assert.strictEqual(util.isTypescript("test.ts.map"), false); - assert.strictEqual(util.isTypescript("test.ts-js"), false); - assert.strictEqual(util.isTypescript("ts"), false); - }); - - it("should return true", () => { - assert.strictEqual(util.isTypescript("test.ts"), true); - assert.strictEqual(util.isTypescript("test.tsx"), true); - assert.strictEqual(util.isTypescript("test.TS"), true); - assert.strictEqual(util.isTypescript("test.TSX"), true); - assert.strictEqual(util.isTypescript("test.d.ts"), true); - assert.strictEqual(util.isTypescript("test.d.tsx"), true); - assert.strictEqual(util.isTypescript("test.D.TS"), true); - assert.strictEqual(util.isTypescript("test.D.TSX"), true); + it("returns false for non-typescript files", () => { + const invalid = [ + "test.js", + "test.jsx", + "README.md", + "test.d.js", + "test.ts.js", + "test.ts.map", + "test.ts-js", + "ts", + ]; + + invalid.forEach(f => { + assert.strictEqual(util.isTypescript(f), false); + }); + }); + + it("returns true for typescript files", () => { + const valid = [ + "test.ts", + "test.tsx", + "test.TS", + "test.TSX", + "test.d.ts", + "test.d.tsx", + "test.D.TS", + "test.D.TSX", + ]; + + valid.forEach(f => { + assert.strictEqual(util.isTypescript(f), true); + }); }); }); describe("isDefinitionFile", () => { - it("should return false", () => { - assert.strictEqual(util.isDefinitionFile("test.js"), false); - assert.strictEqual(util.isDefinitionFile("test.jsx"), false); - assert.strictEqual(util.isDefinitionFile("README.md"), false); - assert.strictEqual(util.isDefinitionFile("test.d.js"), false); - assert.strictEqual(util.isDefinitionFile("test.ts.js"), false); - assert.strictEqual(util.isDefinitionFile("test.ts.map"), false); - assert.strictEqual(util.isDefinitionFile("test.ts-js"), false); - assert.strictEqual(util.isDefinitionFile("test.ts"), false); - assert.strictEqual(util.isDefinitionFile("ts"), false); - assert.strictEqual(util.isDefinitionFile("test.tsx"), false); - assert.strictEqual(util.isDefinitionFile("test.TS"), false); - assert.strictEqual(util.isDefinitionFile("test.TSX"), false); - }); - - it("should return true", () => { - assert.strictEqual(util.isDefinitionFile("test.d.ts"), true); - assert.strictEqual(util.isDefinitionFile("test.d.tsx"), true); - assert.strictEqual(util.isDefinitionFile("test.D.TS"), true); - assert.strictEqual(util.isDefinitionFile("test.D.TSX"), true); + it("returns false for non-definition files", () => { + const invalid = [ + "test.js", + "test.jsx", + "README.md", + "test.d.js", + "test.ts.js", + "test.ts.map", + "test.ts-js", + "test.ts", + "ts", + "test.tsx", + "test.TS", + "test.TSX", + ]; + + invalid.forEach(f => { + assert.strictEqual(util.isDefinitionFile(f), false); + }); + }); + + it("returns true for definition files", () => { + const valid = ["test.d.ts", "test.d.tsx", "test.D.TS", "test.D.TSX"]; + + valid.forEach(f => { + assert.strictEqual(util.isDefinitionFile(f), true); + }); + }); +}); + +describe("deepMerge", () => { + it("creates a brand new object", () => { + const a = {}; + const b = {}; + const result = util.deepMerge(a, b); + + assert.notStrictEqual(result, a); + assert.notStrictEqual(result, b); + }); + + it("deeply merges objects", () => { + const a = { + stringA1: "asdf", + numberA1: 1, + boolA1: true, + arrayA1: [1, 2, 3], + objA1: { + stringA2: "fsda", + numberA2: 2, + boolA2: false, + arrayA2: [3, 2, 1], + objA2: {}, + }, + }; + const b = { + stringB1: "asdf", + numberB1: 1, + boolB1: true, + arrayB1: [1, 2, 3], + objB1: { + stringB2: "fsda", + numberB2: 2, + boolB2: false, + arrayB2: [3, 2, 1], + objB2: {}, + }, + }; + + assert.deepStrictEqual(util.deepMerge(a, b), Object.assign({}, a, b)); + }); + + it("deeply overwrites properties in the first one with the second", () => { + const a = { + prop1: { + prop2: "hi", + }, + }; + const b = { + prop1: { + prop2: "bye", + }, + }; + + assert.deepStrictEqual(util.deepMerge(a, b), b); + }); +}); + +describe("applyDefault", () => { + it("returns a clone of the default if no options given", () => { + const defaults = [ + { + prop: "setting", + }, + ]; + const user = null; + const result = util.applyDefault(defaults, user); + + assert.deepStrictEqual(result, defaults); + assert.notStrictEqual(result, defaults); + }); + + it("returns applies a deepMerge to each element in the array", () => { + const defaults = [ + { + prop: "setting1", + }, + { + prop: "setting2", + }, + ]; + const user = [ + { + prop: "new", + other: "something", + }, + ]; + const result = util.applyDefault(defaults, user); + + assert.deepStrictEqual(result, [ + { + prop: "new", + other: "something", + }, + { + prop: "setting2", + }, + ]); + assert.notStrictEqual(result, defaults); + assert.notStrictEqual(result, user); + }); + + it("returns a brand new array", () => { + const defaults = []; + const user = []; + const result = util.applyDefault(defaults, user); + + assert.notStrictEqual(result, defaults); + assert.notStrictEqual(result, user); + }); +}); + +describe("upperCaseFirst", () => { + it("upper cases first", () => { + assert.strictEqual(util.upperCaseFirst("hello"), "Hello"); }); }); diff --git a/packages/eslint-plugin-typescript/tools/update-recommended.js b/packages/eslint-plugin-typescript/tools/update-recommended.js index 0fb8266a989..8d30d03112a 100644 --- a/packages/eslint-plugin-typescript/tools/update-recommended.js +++ b/packages/eslint-plugin-typescript/tools/update-recommended.js @@ -1,9 +1,18 @@ +/* eslint-disable no-console */ "use strict"; const path = require("path"); const fs = require("fs"); const requireIndex = require("requireindex"); +const bannedRecommendedRules = new Set([ + "camelcase", + "indent", + "no-array-constructor", + "no-unused-vars", +]); +const MAX_RULE_NAME_LENGTH = 32 + "typescript/".length; + /** * Generate recommended configuration * @returns {void} @@ -11,16 +20,40 @@ const requireIndex = require("requireindex"); function generate() { // replace this with Object.entries when node > 8 const allRules = requireIndex(path.resolve(__dirname, "../lib/rules")); + const rules = Object.keys(allRules) - .filter(key => allRules[key].meta.docs.recommended) - .reduce((config, item) => { - config[item] = "error"; + .filter(key => !!allRules[key].meta.docs.recommended) + .reduce((config, key) => { + // having this here is just for output niceness (the keys will be ordered) + if (bannedRecommendedRules.has(key)) { + console.log(key.padEnd(MAX_RULE_NAME_LENGTH), "= off"); + config[key] = "off"; + } + + const ruleName = `typescript/${key}`; + const setting = allRules[key].meta.docs.recommended; + + console.log(ruleName.padEnd(MAX_RULE_NAME_LENGTH), "=", setting); + config[ruleName] = setting; + return config; }, {}); const filePath = path.resolve(__dirname, "../lib/configs/recommended.json"); - fs.writeFileSync(filePath, `${JSON.stringify({ rules }, null, 4)}\n`); + const recommendedConfig = { + parser: "eslint-plugin-typescript/parser", + parserOptions: { + sourceType: "module", + }, + plugins: ["typescript"], + rules, + }; + + fs.writeFileSync( + filePath, + `${JSON.stringify(recommendedConfig, null, 4)}\n` + ); } generate();