diff --git a/.eslintrc.js b/.eslintrc.js
index 20b6b1f86903..42d8a09253b1 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -28,9 +28,16 @@ module.exports = {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-var-requires': 'off',
+ '@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
'@typescript-eslint/unbound-method': 'off',
+ 'no-empty-function': 'off',
+ '@typescript-eslint/no-empty-function': [
+ 'error',
+ { allow: ['arrowFunctions'] },
+ ],
+
//
// eslint base
//
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f3790b06c31..19ec0dfaae03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+
+### Bug Fixes
+
+* **eslint-plugin:** [no-empty-function] add missed node types ([#1271](https://github.com/typescript-eslint/typescript-eslint/issues/1271)) ([e9d44f5](https://github.com/typescript-eslint/typescript-eslint/commit/e9d44f5))
+* **eslint-plugin:** [no-untyped-pub-sig] ignore set return ([#1264](https://github.com/typescript-eslint/typescript-eslint/issues/1264)) ([6daff10](https://github.com/typescript-eslint/typescript-eslint/commit/6daff10))
+* **eslint-plugin:** [no-unused-expressions] ignore directives ([#1285](https://github.com/typescript-eslint/typescript-eslint/issues/1285)) ([ce4c803](https://github.com/typescript-eslint/typescript-eslint/commit/ce4c803))
+* **eslint-plugin:** [prefer-optional-chain] allow $ in identifiers ([c72c3c1](https://github.com/typescript-eslint/typescript-eslint/commit/c72c3c1))
+* **eslint-plugin:** [prefer-optional-chain] handle more cases ([#1261](https://github.com/typescript-eslint/typescript-eslint/issues/1261)) ([57ddba3](https://github.com/typescript-eslint/typescript-eslint/commit/57ddba3))
+* **eslint-plugin:** [return-await] allow Any and Unknown ([#1270](https://github.com/typescript-eslint/typescript-eslint/issues/1270)) ([ebf5e0a](https://github.com/typescript-eslint/typescript-eslint/commit/ebf5e0a))
+* **eslint-plugin:** [strict-bool-expr] allow nullish coalescing ([#1275](https://github.com/typescript-eslint/typescript-eslint/issues/1275)) ([3b39340](https://github.com/typescript-eslint/typescript-eslint/commit/3b39340))
+* **typescript-estree:** make FunctionDeclaration.body non-null ([#1288](https://github.com/typescript-eslint/typescript-eslint/issues/1288)) ([dc73510](https://github.com/typescript-eslint/typescript-eslint/commit/dc73510))
+
+
+### Features
+
+* **eslint-plugin:** [no-empty-func] private/protected construct ([#1267](https://github.com/typescript-eslint/typescript-eslint/issues/1267)) ([3b931ac](https://github.com/typescript-eslint/typescript-eslint/commit/3b931ac))
+* **eslint-plugin:** [no-non-null-assert] add suggestion fixer ([#1260](https://github.com/typescript-eslint/typescript-eslint/issues/1260)) ([e350a21](https://github.com/typescript-eslint/typescript-eslint/commit/e350a21))
+* **eslint-plugin:** [no-unnec-cond] support nullish coalescing ([#1148](https://github.com/typescript-eslint/typescript-eslint/issues/1148)) ([96ef1e7](https://github.com/typescript-eslint/typescript-eslint/commit/96ef1e7))
+* **eslint-plugin:** [prefer-null-coal] opt for suggestion fixer ([#1272](https://github.com/typescript-eslint/typescript-eslint/issues/1272)) ([f84eb96](https://github.com/typescript-eslint/typescript-eslint/commit/f84eb96))
+* **experimental-utils:** add isSpaceBetween declaration to Sou… ([#1268](https://github.com/typescript-eslint/typescript-eslint/issues/1268)) ([f83f04b](https://github.com/typescript-eslint/typescript-eslint/commit/f83f04b))
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
diff --git a/lerna.json b/lerna.json
index 3881b978c009..b81dc17b54b1 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.9.0",
+ "version": "2.10.0",
"npmClient": "yarn",
"useWorkspaces": true,
"stream": true
diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md
index 634660606399..77dd508e5f6a 100644
--- a/packages/eslint-plugin-tslint/CHANGELOG.md
+++ b/packages/eslint-plugin-tslint/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint
diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json
index f9011094ab1b..58cc56a13d50 100644
--- a/packages/eslint-plugin-tslint/package.json
+++ b/packages/eslint-plugin-tslint/package.json
@@ -1,6 +1,6 @@
{
"name": "@typescript-eslint/eslint-plugin-tslint",
- "version": "2.9.0",
+ "version": "2.10.0",
"main": "dist/index.js",
"typings": "src/index.ts",
"description": "TSLint wrapper plugin for ESLint",
@@ -31,7 +31,7 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
- "@typescript-eslint/experimental-utils": "2.9.0",
+ "@typescript-eslint/experimental-utils": "2.10.0",
"lodash.memoize": "^4.1.2"
},
"peerDependencies": {
@@ -41,6 +41,6 @@
},
"devDependencies": {
"@types/lodash.memoize": "^4.1.4",
- "@typescript-eslint/parser": "2.9.0"
+ "@typescript-eslint/parser": "2.10.0"
}
}
diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts
index 23ff428111d2..315cf0fb4a2e 100644
--- a/packages/eslint-plugin-tslint/src/rules/config.ts
+++ b/packages/eslint-plugin-tslint/src/rules/config.ts
@@ -47,8 +47,8 @@ const tslintConfig = memoize(
return Configuration.loadConfigurationFromPath(lintFile);
}
return Configuration.parseConfigFile({
- rules: tslintRules || {},
- rulesDirectory: tslintRulesDirectory || [],
+ rules: tslintRules ?? {},
+ rulesDirectory: tslintRulesDirectory ?? [],
});
},
(lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) =>
diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md
index 8bd348828083..dee39704de43 100644
--- a/packages/eslint-plugin/CHANGELOG.md
+++ b/packages/eslint-plugin/CHANGELOG.md
@@ -3,6 +3,31 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+
+### Bug Fixes
+
+* **eslint-plugin:** [no-empty-function] add missed node types ([#1271](https://github.com/typescript-eslint/typescript-eslint/issues/1271)) ([e9d44f5](https://github.com/typescript-eslint/typescript-eslint/commit/e9d44f5))
+* **eslint-plugin:** [no-untyped-pub-sig] ignore set return ([#1264](https://github.com/typescript-eslint/typescript-eslint/issues/1264)) ([6daff10](https://github.com/typescript-eslint/typescript-eslint/commit/6daff10))
+* **eslint-plugin:** [no-unused-expressions] ignore directives ([#1285](https://github.com/typescript-eslint/typescript-eslint/issues/1285)) ([ce4c803](https://github.com/typescript-eslint/typescript-eslint/commit/ce4c803))
+* **eslint-plugin:** [prefer-optional-chain] allow $ in identifiers ([c72c3c1](https://github.com/typescript-eslint/typescript-eslint/commit/c72c3c1))
+* **eslint-plugin:** [prefer-optional-chain] handle more cases ([#1261](https://github.com/typescript-eslint/typescript-eslint/issues/1261)) ([57ddba3](https://github.com/typescript-eslint/typescript-eslint/commit/57ddba3))
+* **eslint-plugin:** [return-await] allow Any and Unknown ([#1270](https://github.com/typescript-eslint/typescript-eslint/issues/1270)) ([ebf5e0a](https://github.com/typescript-eslint/typescript-eslint/commit/ebf5e0a))
+* **eslint-plugin:** [strict-bool-expr] allow nullish coalescing ([#1275](https://github.com/typescript-eslint/typescript-eslint/issues/1275)) ([3b39340](https://github.com/typescript-eslint/typescript-eslint/commit/3b39340))
+
+
+### Features
+
+* **eslint-plugin:** [no-empty-func] private/protected construct ([#1267](https://github.com/typescript-eslint/typescript-eslint/issues/1267)) ([3b931ac](https://github.com/typescript-eslint/typescript-eslint/commit/3b931ac))
+* **eslint-plugin:** [no-non-null-assert] add suggestion fixer ([#1260](https://github.com/typescript-eslint/typescript-eslint/issues/1260)) ([e350a21](https://github.com/typescript-eslint/typescript-eslint/commit/e350a21))
+* **eslint-plugin:** [no-unnec-cond] support nullish coalescing ([#1148](https://github.com/typescript-eslint/typescript-eslint/issues/1148)) ([96ef1e7](https://github.com/typescript-eslint/typescript-eslint/commit/96ef1e7))
+* **eslint-plugin:** [prefer-null-coal] opt for suggestion fixer ([#1272](https://github.com/typescript-eslint/typescript-eslint/issues/1272)) ([f84eb96](https://github.com/typescript-eslint/typescript-eslint/commit/f84eb96))
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
diff --git a/packages/eslint-plugin/docs/rules/no-empty-function.md b/packages/eslint-plugin/docs/rules/no-empty-function.md
index e06961d4259b..2b37c4da396e 100644
--- a/packages/eslint-plugin/docs/rules/no-empty-function.md
+++ b/packages/eslint-plugin/docs/rules/no-empty-function.md
@@ -44,4 +44,37 @@ See the [ESLint documentation](https://eslint.org/docs/rules/no-empty-function)
}
```
+## Options
+
+This rule has an object option:
+
+- `allow` (`string[]`)
+ - `"protected-constructors"` - Protected class constructors.
+ - `"private-constructors"` - Private class constructors.
+ - [See the other options allowed](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md#options)
+
+#### allow: protected-constructors
+
+Examples of **correct** code for the `{ "allow": ["protected-constructors"] }` option:
+
+```ts
+/*eslint @typescript-eslint/no-empty-function: ["error", { "allow": ["protected-constructors"] }]*/
+
+class Foo {
+ protected constructor() {}
+}
+```
+
+#### allow: private-constructors
+
+Examples of **correct** code for the `{ "allow": ["private-constructors"] }` option:
+
+```ts
+/*eslint @typescript-eslint/no-empty-function: ["error", { "allow": ["private-constructors"] }]*/
+
+class Foo {
+ private constructor() {}
+}
+```
+
Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md)
diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md
index 140ade2dbe7c..f0ef0872d4eb 100644
--- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md
+++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md
@@ -46,13 +46,15 @@ type Options = [
{
ignoreConditionalTests?: boolean;
ignoreMixedLogicalExpressions?: boolean;
+ forceSuggestionFixer?: boolean;
},
];
const defaultOptions = [
{
ignoreConditionalTests: true,
- ignoreMixedLogicalExpressions: true;
+ ignoreMixedLogicalExpressions: true,
+ forceSuggestionFixer: false,
},
];
```
@@ -129,7 +131,13 @@ a ?? (b && c) ?? d;
a ?? (b && c && d);
```
-**_NOTE:_** Errors for this specific case will be presented as suggestions, instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression.
+**_NOTE:_** Errors for this specific case will be presented as suggestions (see below), instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression.
+
+### forceSuggestionFixer
+
+Setting this option to `true` will cause the rule to use ESLint's "suggested fix" mode for all fixes. _This option is provided as to aid in transitioning your codebase onto this rule_.
+
+Suggestion fixes cannot be automatically applied via the `--fix` CLI command, but can be _manually_ chosen to be applied one at a time via an IDE or similar. This makes it safe to run autofixers on an existing codebase without worrying about potential runtime behaviour changes from this rule's fixer.
## When Not To Use It
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index 551199641903..94ff71ea8ef7 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@typescript-eslint/eslint-plugin",
- "version": "2.9.0",
+ "version": "2.10.0",
"description": "TypeScript plugin for ESLint",
"keywords": [
"eslint",
@@ -40,7 +40,7 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
- "@typescript-eslint/experimental-utils": "2.9.0",
+ "@typescript-eslint/experimental-utils": "2.10.0",
"eslint-utils": "^1.4.3",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
diff --git a/packages/eslint-plugin/src/configs/README.md b/packages/eslint-plugin/src/configs/README.md
index 28747608efb7..48b3f27444ef 100644
--- a/packages/eslint-plugin/src/configs/README.md
+++ b/packages/eslint-plugin/src/configs/README.md
@@ -8,7 +8,7 @@ TODO when all config is added.
## eslint-recommended
-The `eslint-recommended` ruleset is meant to be used after extending `eslint:recommended`. It disables rules that are already checked by the Typescript compiler and enables rules that promote using more the more modern constructs Typescript allows for.
+The `eslint-recommended` ruleset is meant to be used after extending `eslint:recommended`. It disables rules that are already checked by the Typescript compiler and enables rules that promote using the more modern constructs Typescript allows for.
```cjson
{
diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts
index 0ed4266a240d..de3cca753436 100644
--- a/packages/eslint-plugin/src/rules/array-type.ts
+++ b/packages/eslint-plugin/src/rules/array-type.ts
@@ -127,7 +127,7 @@ export default util.createRule({
const sourceCode = context.getSourceCode();
const defaultOption = options.default;
- const readonlyOption = options.readonly || defaultOption;
+ const readonlyOption = options.readonly ?? defaultOption;
const isArraySimpleOption =
defaultOption === 'array-simple' && readonlyOption === 'array-simple';
diff --git a/packages/eslint-plugin/src/rules/camelcase.ts b/packages/eslint-plugin/src/rules/camelcase.ts
index a2be8a1c6958..17b88d7e93d9 100644
--- a/packages/eslint-plugin/src/rules/camelcase.ts
+++ b/packages/eslint-plugin/src/rules/camelcase.ts
@@ -52,10 +52,11 @@ export default util.createRule({
const genericType = options.genericType;
const properties = options.properties;
- const allow = (options.allow || []).map(entry => ({
- name: entry,
- regex: new RegExp(entry),
- }));
+ const allow =
+ options.allow?.map(entry => ({
+ name: entry,
+ regex: new RegExp(entry),
+ })) ?? [];
/**
* Checks if a string contains an underscore and isn't all upper-case
diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts
index cf9e851c81cf..f90d1c566130 100644
--- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts
+++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts
@@ -36,7 +36,7 @@ export default util.createRule({
node: node.id,
messageId: 'interfaceOverType',
fix(fixer) {
- const typeNode = node.typeParameters || node.id;
+ const typeNode = node.typeParameters ?? node.id;
const fixes: TSESLint.RuleFix[] = [];
const firstToken = sourceCode.getFirstToken(node);
@@ -70,7 +70,7 @@ export default util.createRule({
node: node.id,
messageId: 'typeOverInterface',
fix(fixer) {
- const typeNode = node.typeParameters || node.id;
+ const typeNode = node.typeParameters ?? node.id;
const fixes: TSESLint.RuleFix[] = [];
const firstToken = sourceCode.getFirstToken(node);
diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts
index 864c630da178..3d44d0b61acd 100644
--- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts
+++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts
@@ -75,14 +75,14 @@ export default util.createRule({
defaultOptions: [{ accessibility: 'explicit' }],
create(context, [option]) {
const sourceCode = context.getSourceCode();
- const baseCheck: AccessibilityLevel = option.accessibility || 'explicit';
- const overrides = option.overrides || {};
- const ctorCheck = overrides.constructors || baseCheck;
- const accessorCheck = overrides.accessors || baseCheck;
- const methodCheck = overrides.methods || baseCheck;
- const propCheck = overrides.properties || baseCheck;
- const paramPropCheck = overrides.parameterProperties || baseCheck;
- const ignoredMethodNames = new Set(option.ignoredMethodNames || []);
+ const baseCheck: AccessibilityLevel = option.accessibility ?? 'explicit';
+ const overrides = option.overrides ?? {};
+ const ctorCheck = overrides.constructors ?? baseCheck;
+ const accessorCheck = overrides.accessors ?? baseCheck;
+ const methodCheck = overrides.methods ?? baseCheck;
+ const propCheck = overrides.properties ?? baseCheck;
+ const paramPropCheck = overrides.parameterProperties ?? baseCheck;
+ const ignoredMethodNames = new Set(option.ignoredMethodNames ?? []);
/**
* Generates the report for rule violations
*/
diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts
index 3a19936b7123..d76204c3fef5 100644
--- a/packages/eslint-plugin/src/rules/func-call-spacing.ts
+++ b/packages/eslint-plugin/src/rules/func-call-spacing.ts
@@ -82,7 +82,7 @@ export default util.createRule({
const closingParenToken = sourceCode.getLastToken(node)!;
const lastCalleeTokenWithoutPossibleParens = sourceCode.getLastToken(
- node.typeParameters || node.callee,
+ node.typeParameters ?? node.callee,
)!;
const openingParenToken = sourceCode.getFirstTokenBetween(
lastCalleeTokenWithoutPossibleParens,
diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts
index d1efe5e11330..7440159286f8 100644
--- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts
+++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts
@@ -104,7 +104,7 @@ export default util.createRule({
// use the base options as the defaults for the cases
const baseOptions = options;
- const overrides = baseOptions.overrides || {};
+ const overrides = baseOptions.overrides ?? {};
const interfaceOptions: BaseOptions = util.deepMerge(
baseOptions,
overrides.interface,
@@ -227,7 +227,7 @@ export default util.createRule({
const opts = isSingleLine ? typeOpts.singleline : typeOpts.multiline;
members.forEach((member, index) => {
- checkLastToken(member, opts || {}, index === members.length - 1);
+ checkLastToken(member, opts ?? {}, index === members.length - 1);
});
}
diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts
index b17076856949..c8911292836b 100644
--- a/packages/eslint-plugin/src/rules/member-ordering.ts
+++ b/packages/eslint-plugin/src/rules/member-ordering.ts
@@ -388,28 +388,28 @@ export default util.createRule({
ClassDeclaration(node): void {
validateMembersOrder(
node.body.body,
- options.classes || options.default!,
+ options.classes ?? options.default!,
true,
);
},
ClassExpression(node): void {
validateMembersOrder(
node.body.body,
- options.classExpressions || options.default!,
+ options.classExpressions ?? options.default!,
true,
);
},
TSInterfaceDeclaration(node): void {
validateMembersOrder(
node.body.body,
- options.interfaces || options.default!,
+ options.interfaces ?? options.default!,
false,
);
},
TSTypeLiteral(node): void {
validateMembersOrder(
node.members,
- options.typeLiterals || options.default!,
+ options.typeLiterals ?? options.default!,
false,
);
},
diff --git a/packages/eslint-plugin/src/rules/no-empty-function.ts b/packages/eslint-plugin/src/rules/no-empty-function.ts
index 31aa5d8bb630..17f238abec1d 100644
--- a/packages/eslint-plugin/src/rules/no-empty-function.ts
+++ b/packages/eslint-plugin/src/rules/no-empty-function.ts
@@ -8,6 +8,32 @@ import * as util from '../util';
type Options = util.InferOptionsTypeFromRule;
type MessageIds = util.InferMessageIdsTypeFromRule;
+const schema = util.deepMerge(
+ Array.isArray(baseRule.meta.schema)
+ ? baseRule.meta.schema[0]
+ : baseRule.meta.schema,
+ {
+ properties: {
+ allow: {
+ items: {
+ enum: [
+ 'functions',
+ 'arrowFunctions',
+ 'generatorFunctions',
+ 'methods',
+ 'generatorMethods',
+ 'getters',
+ 'setters',
+ 'constructors',
+ 'private-constructors',
+ 'protected-constructors',
+ ],
+ },
+ },
+ },
+ },
+);
+
export default util.createRule({
name: 'no-empty-function',
meta: {
@@ -17,7 +43,7 @@ export default util.createRule({
category: 'Best Practices',
recommended: 'error',
},
- schema: baseRule.meta.schema,
+ schema: [schema],
messages: baseRule.meta.messages,
},
defaultOptions: [
@@ -25,24 +51,13 @@ export default util.createRule({
allow: [],
},
],
- create(context) {
+ create(context, [{ allow = [] }]) {
const rules = baseRule.create(context);
- /**
- * Checks if the node is a constructor
- * @param node the node to ve validated
- * @returns true if the node is a constructor
- * @private
- */
- function isConstructor(
- node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
- ): boolean {
- return !!(
- node.parent &&
- node.parent.type === 'MethodDefinition' &&
- node.parent.kind === 'constructor'
- );
- }
+ const isAllowedProtectedConstructors = allow.includes(
+ 'protected-constructors',
+ );
+ const isAllowedPrivateConstructors = allow.includes('private-constructors');
/**
* Check if the method body is empty
@@ -74,30 +89,42 @@ export default util.createRule({
}
/**
- * Checks if the method is a concise constructor (no function body, but has parameter properties)
* @param node the node to be validated
- * @returns true if the method is a concise constructor
+ * @returns true if the constructor is allowed to be empty
* @private
*/
- function isConciseConstructor(
+ function isAllowedEmptyConstructor(
node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
): boolean {
- // Check TypeScript specific nodes
- return (
- isConstructor(node) && isBodyEmpty(node) && hasParameterProperties(node)
- );
+ const parent = node.parent;
+ if (
+ isBodyEmpty(node) &&
+ parent?.type === 'MethodDefinition' &&
+ parent.kind === 'constructor'
+ ) {
+ const { accessibility } = parent;
+
+ return (
+ // allow protected constructors
+ (accessibility === 'protected' && isAllowedProtectedConstructors) ||
+ // allow private constructors
+ (accessibility === 'private' && isAllowedPrivateConstructors) ||
+ // allow constructors which have parameter properties
+ hasParameterProperties(node)
+ );
+ }
+
+ return false;
}
return {
- FunctionDeclaration(node): void {
- if (!isConciseConstructor(node)) {
- rules.FunctionDeclaration(node);
- }
- },
+ ...rules,
FunctionExpression(node): void {
- if (!isConciseConstructor(node)) {
- rules.FunctionExpression(node);
+ if (isAllowedEmptyConstructor(node)) {
+ return;
}
+
+ rules.FunctionExpression(node);
},
};
},
diff --git a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts
index 4e888c58bb64..ef268d7a4e2f 100644
--- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts
+++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts
@@ -1,6 +1,12 @@
+import {
+ TSESLint,
+ AST_NODE_TYPES,
+} from '@typescript-eslint/experimental-utils';
import * as util from '../util';
-export default util.createRule({
+type MessageIds = 'noNonNull' | 'suggestOptionalChain';
+
+export default util.createRule<[], MessageIds>({
name: 'no-non-null-assertion',
meta: {
type: 'problem',
@@ -12,16 +18,106 @@ export default util.createRule({
},
messages: {
noNonNull: 'Forbidden non-null assertion.',
+ suggestOptionalChain:
+ 'Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator.',
},
schema: [],
},
defaultOptions: [],
create(context) {
+ const sourceCode = context.getSourceCode();
return {
TSNonNullExpression(node): void {
+ const suggest: TSESLint.ReportSuggestionArray = [];
+ function convertTokenToOptional(
+ replacement: '?' | '?.',
+ ): TSESLint.ReportFixFunction {
+ return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => {
+ const operator = sourceCode.getTokenAfter(
+ node.expression,
+ util.isNonNullAssertionPunctuator,
+ );
+ if (operator) {
+ return fixer.replaceText(operator, replacement);
+ }
+
+ return null;
+ };
+ }
+ function removeToken(): TSESLint.ReportFixFunction {
+ return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => {
+ const operator = sourceCode.getTokenAfter(
+ node.expression,
+ util.isNonNullAssertionPunctuator,
+ );
+ if (operator) {
+ return fixer.remove(operator);
+ }
+
+ return null;
+ };
+ }
+
+ if (node.parent) {
+ if (
+ (node.parent.type === AST_NODE_TYPES.MemberExpression ||
+ node.parent.type === AST_NODE_TYPES.OptionalMemberExpression) &&
+ node.parent.object === node
+ ) {
+ if (!node.parent.optional) {
+ if (node.parent.computed) {
+ // it is x![y]?.z
+ suggest.push({
+ messageId: 'suggestOptionalChain',
+ fix: convertTokenToOptional('?.'),
+ });
+ } else {
+ // it is x!.y?.z
+ suggest.push({
+ messageId: 'suggestOptionalChain',
+ fix: convertTokenToOptional('?'),
+ });
+ }
+ } else {
+ if (node.parent.computed) {
+ // it is x!?.[y].z
+ suggest.push({
+ messageId: 'suggestOptionalChain',
+ fix: removeToken(),
+ });
+ } else {
+ // it is x!?.y.z
+ suggest.push({
+ messageId: 'suggestOptionalChain',
+ fix: removeToken(),
+ });
+ }
+ }
+ } else if (
+ (node.parent.type === AST_NODE_TYPES.CallExpression ||
+ node.parent.type === AST_NODE_TYPES.OptionalCallExpression) &&
+ node.parent.callee === node
+ ) {
+ if (!node.parent.optional) {
+ // it is x.y?.z!()
+ suggest.push({
+ messageId: 'suggestOptionalChain',
+ fix: convertTokenToOptional('?.'),
+ });
+ } else {
+ // it is x.y.z!?.()
+ suggest.push({
+ messageId: 'suggestOptionalChain',
+ fix: removeToken(),
+ });
+ }
+ }
+ }
+
context.report({
node,
messageId: 'noNonNull',
+ suggest,
});
},
};
diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
index 420271a23b86..1ee2c2629806 100644
--- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
+++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts
@@ -31,6 +31,17 @@ const isPossiblyFalsy = (type: ts.Type): boolean =>
const isPossiblyTruthy = (type: ts.Type): boolean =>
unionTypeParts(type).some(type => !isFalsyType(type));
+// Nullish utilities
+const nullishFlag = ts.TypeFlags.Undefined | ts.TypeFlags.Null;
+const isNullishType = (type: ts.Type): boolean =>
+ isTypeFlagSet(type, nullishFlag);
+
+const isPossiblyNullish = (type: ts.Type): boolean =>
+ unionTypeParts(type).some(isNullishType);
+
+const isAlwaysNullish = (type: ts.Type): boolean =>
+ unionTypeParts(type).every(isNullishType);
+
// isLiteralType only covers numbers and strings, this is a more exhaustive check.
const isLiteral = (type: ts.Type): boolean =>
isBooleanLiteralType(type, true) ||
@@ -51,6 +62,8 @@ export type Options = [
export type MessageId =
| 'alwaysTruthy'
| 'alwaysFalsy'
+ | 'neverNullish'
+ | 'alwaysNullish'
| 'literalBooleanExpression'
| 'never';
export default createRule({
@@ -81,6 +94,10 @@ export default createRule({
messages: {
alwaysTruthy: 'Unnecessary conditional, value is always truthy.',
alwaysFalsy: 'Unnecessary conditional, value is always falsy.',
+ neverNullish:
+ 'Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.',
+ alwaysNullish:
+ 'Unnecessary conditional, left-hand side of `??` operator is always `null` or `undefined`',
literalBooleanExpression:
'Unnecessary conditional, both sides of the expression are literal values',
never: 'Unnecessary conditional, value is `never`',
@@ -120,12 +137,35 @@ export default createRule({
) {
return;
}
- if (isTypeFlagSet(type, TypeFlags.Never)) {
- context.report({ node, messageId: 'never' });
- } else if (!isPossiblyTruthy(type)) {
- context.report({ node, messageId: 'alwaysFalsy' });
- } else if (!isPossiblyFalsy(type)) {
- context.report({ node, messageId: 'alwaysTruthy' });
+ const messageId = isTypeFlagSet(type, TypeFlags.Never)
+ ? 'never'
+ : !isPossiblyTruthy(type)
+ ? 'alwaysFalsy'
+ : !isPossiblyFalsy(type)
+ ? 'alwaysTruthy'
+ : undefined;
+
+ if (messageId) {
+ context.report({ node, messageId });
+ }
+ }
+
+ function checkNodeForNullish(node: TSESTree.Node): void {
+ const type = getNodeType(node);
+ // Conditional is always necessary if it involves `any` or `unknown`
+ if (isTypeFlagSet(type, TypeFlags.Any | TypeFlags.Unknown)) {
+ return;
+ }
+ const messageId = isTypeFlagSet(type, TypeFlags.Never)
+ ? 'never'
+ : !isPossiblyNullish(type)
+ ? 'neverNullish'
+ : isAlwaysNullish(type)
+ ? 'alwaysNullish'
+ : undefined;
+
+ if (messageId) {
+ context.report({ node, messageId });
}
}
@@ -178,6 +218,10 @@ export default createRule({
function checkLogicalExpressionForUnnecessaryConditionals(
node: TSESTree.LogicalExpression,
): void {
+ if (node.operator === '??') {
+ checkNodeForNullish(node.left);
+ return;
+ }
checkNode(node.left);
if (!ignoreRhs) {
checkNode(node.right);
diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts
index 1017be32222f..45508e4692e2 100644
--- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts
+++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts
@@ -40,7 +40,7 @@ export default util.createRule({
}
function symbolIsNamespaceInScope(symbol: ts.Symbol): boolean {
- const symbolDeclarations = symbol.getDeclarations() || [];
+ const symbolDeclarations = symbol.getDeclarations() ?? [];
if (
symbolDeclarations.some(decl =>
diff --git a/packages/eslint-plugin/src/rules/no-untyped-public-signature.ts b/packages/eslint-plugin/src/rules/no-untyped-public-signature.ts
index 39a635b850e7..b72e43e4cd04 100644
--- a/packages/eslint-plugin/src/rules/no-untyped-public-signature.ts
+++ b/packages/eslint-plugin/src/rules/no-untyped-public-signature.ts
@@ -108,6 +108,7 @@ export default util.createRule({
if (
node.kind !== 'constructor' &&
+ node.kind !== 'set' &&
!isReturnTyped(node.value.returnType)
) {
context.report({
diff --git a/packages/eslint-plugin/src/rules/no-unused-expressions.ts b/packages/eslint-plugin/src/rules/no-unused-expressions.ts
index f7d86f50c1c7..b2eb6f8e30b7 100644
--- a/packages/eslint-plugin/src/rules/no-unused-expressions.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-expressions.ts
@@ -12,10 +12,7 @@ export default util.createRule({
recommended: false,
},
schema: baseRule.meta.schema,
- messages: {
- expected:
- 'Expected an assignment or function call and instead saw an expression.',
- },
+ messages: {},
},
defaultOptions: [],
create(context) {
@@ -23,9 +20,13 @@ export default util.createRule({
return {
ExpressionStatement(node): void {
- if (node.expression.type === AST_NODE_TYPES.OptionalCallExpression) {
+ if (
+ node.directive ||
+ node.expression.type === AST_NODE_TYPES.OptionalCallExpression
+ ) {
return;
}
+
rules.ExpressionStatement(node);
},
};
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts b/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts
index 1286982895ef..1f75e1f026f0 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts
@@ -78,7 +78,7 @@ export default util.createRule({
? new RegExp(userOptions.ignoredNamesRegex)
: null,
ignoreArgsIfArgsAfterAreUsed:
- userOptions.ignoreArgsIfArgsAfterAreUsed || false,
+ userOptions.ignoreArgsIfArgsAfterAreUsed ?? false,
};
function handleIdentifier(identifier: ts.Identifier): void {
diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts
index afb564025fd7..649c8b3db21c 100644
--- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts
+++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts
@@ -11,6 +11,7 @@ export type Options = [
{
ignoreConditionalTests?: boolean;
ignoreMixedLogicalExpressions?: boolean;
+ forceSuggestionFixer?: boolean;
},
];
export type MessageIds = 'preferNullish';
@@ -41,6 +42,9 @@ export default util.createRule({
ignoreMixedLogicalExpressions: {
type: 'boolean',
},
+ forceSuggestionFixer: {
+ type: 'boolean',
+ },
},
additionalProperties: false,
},
@@ -50,9 +54,19 @@ export default util.createRule({
{
ignoreConditionalTests: true,
ignoreMixedLogicalExpressions: true,
+ forceSuggestionFixer: false,
},
],
- create(context, [{ ignoreConditionalTests, ignoreMixedLogicalExpressions }]) {
+ create(
+ context,
+ [
+ {
+ ignoreConditionalTests,
+ ignoreMixedLogicalExpressions,
+ forceSuggestionFixer,
+ },
+ ],
+ ) {
const parserServices = util.getParserServices(context);
const sourceCode = context.getSourceCode();
const checker = parserServices.program.getTypeChecker();
@@ -79,30 +93,34 @@ export default util.createRule({
return;
}
- const barBarOperator = sourceCode.getTokenAfter(
- node.left,
- token =>
- token.type === AST_TOKEN_TYPES.Punctuator &&
- token.value === node.operator,
- )!; // there _must_ be an operator
-
- const fixer = isMixedLogical
- ? // suggestion instead for cases where we aren't sure if the fixer is completely safe
- ({
- suggest: [
- {
- messageId: 'preferNullish',
- fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix {
- return fixer.replaceText(barBarOperator, '??');
+ const barBarOperator = util.nullThrows(
+ sourceCode.getTokenAfter(
+ node.left,
+ token =>
+ token.type === AST_TOKEN_TYPES.Punctuator &&
+ token.value === node.operator,
+ ),
+ util.NullThrowsReasons.MissingToken('operator', node.type),
+ );
+
+ const fixer =
+ isMixedLogical || forceSuggestionFixer
+ ? // suggestion instead for cases where we aren't sure if the fixer is completely safe
+ ({
+ suggest: [
+ {
+ messageId: 'preferNullish',
+ fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix {
+ return fixer.replaceText(barBarOperator, '??');
+ },
},
+ ],
+ } as const)
+ : {
+ fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix {
+ return fixer.replaceText(barBarOperator, '??');
},
- ],
- } as const)
- : {
- fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix {
- return fixer.replaceText(barBarOperator, '??');
- },
- };
+ };
context.report({
node: barBarOperator,
diff --git a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts
index a7bcc49a678b..5f8ef5bc0585 100644
--- a/packages/eslint-plugin/src/rules/prefer-optional-chain.ts
+++ b/packages/eslint-plugin/src/rules/prefer-optional-chain.ts
@@ -2,9 +2,17 @@ import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';
+import { isOpeningParenToken } from 'eslint-utils';
import * as util from '../util';
-const WHITESPACE_REGEX = /\s/g;
+type ValidChainTarget =
+ | TSESTree.BinaryExpression
+ | TSESTree.CallExpression
+ | TSESTree.MemberExpression
+ | TSESTree.OptionalCallExpression
+ | TSESTree.OptionalMemberExpression
+ | TSESTree.Identifier
+ | TSESTree.ThisExpression;
/*
The AST is always constructed such the first element is always the deepest element.
@@ -70,19 +78,36 @@ export default util.createRule({
let optionallyChainedCode = previousLeftText;
let expressionCount = 1;
while (current.type === AST_NODE_TYPES.LogicalExpression) {
- if (!isValidChainTarget(current.right)) {
+ if (
+ !isValidChainTarget(
+ current.right,
+ // only allow identifiers for the first chain - foo && foo()
+ expressionCount === 1,
+ )
+ ) {
break;
}
const leftText = previousLeftText;
const rightText = getText(current.right);
- if (!rightText.startsWith(leftText)) {
+ // can't just use startsWith because of cases like foo && fooBar.baz;
+ const matchRegex = new RegExp(
+ `^${
+ // escape regex characters
+ leftText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ }[^a-zA-Z0-9_$]`,
+ );
+ if (
+ !matchRegex.test(rightText) &&
+ // handle redundant cases like foo.bar && foo.bar
+ leftText !== rightText
+ ) {
break;
}
- expressionCount += 1;
// omit weird doubled up expression that make no sense like foo.bar && foo.bar
if (rightText !== leftText) {
+ expressionCount += 1;
previousLeftText = rightText;
/*
@@ -108,21 +133,37 @@ export default util.createRule({
rightText === 'foo.bar.baz[buzz]'
leftText === 'foo.bar.baz'
diff === '[buzz]'
+
+ 5)
+ rightText === 'foo.bar.baz?.buzz'
+ leftText === 'foo.bar.baz'
+ diff === '?.buzz'
*/
const diff = rightText.replace(leftText, '');
- const needsDot = diff.startsWith('(') || diff.startsWith('[');
- optionallyChainedCode += `?${needsDot ? '.' : ''}${diff}`;
+ if (diff.startsWith('?')) {
+ // item was "pre optional chained"
+ optionallyChainedCode += diff;
+ } else {
+ const needsDot = diff.startsWith('(') || diff.startsWith('[');
+ optionallyChainedCode += `?${needsDot ? '.' : ''}${diff}`;
+ }
}
- /* istanbul ignore if: this shouldn't ever happen, but types */
- if (!current.parent) {
- break;
- }
previous = current;
- current = current.parent;
+ current = util.nullThrows(
+ current.parent,
+ util.NullThrowsReasons.MissingParent,
+ );
}
if (expressionCount > 1) {
+ if (previous.right.type === AST_NODE_TYPES.BinaryExpression) {
+ // case like foo && foo.bar !== someValue
+ optionallyChainedCode += ` ${
+ previous.right.operator
+ } ${sourceCode.getText(previous.right.right)}`;
+ }
+
context.report({
node: previous,
messageId: 'preferOptionalChain',
@@ -134,37 +175,191 @@ export default util.createRule({
},
};
- function getText(
- node:
- | TSESTree.BinaryExpression
- | TSESTree.CallExpression
- | TSESTree.Identifier
- | TSESTree.MemberExpression,
+ function getText(node: ValidChainTarget): string {
+ if (node.type === AST_NODE_TYPES.BinaryExpression) {
+ return getText(
+ // isValidChainTarget ensures this is type safe
+ node.left as ValidChainTarget,
+ );
+ }
+
+ if (
+ node.type === AST_NODE_TYPES.CallExpression ||
+ node.type === AST_NODE_TYPES.OptionalCallExpression
+ ) {
+ const calleeText = getText(
+ // isValidChainTarget ensures this is type safe
+ node.callee as ValidChainTarget,
+ );
+
+ // ensure that the call arguments are left untouched, or else we can break cases that _need_ whitespace:
+ // - JSX:
+ // - Unary Operators: typeof foo, await bar, delete baz
+ const closingParenToken = util.nullThrows(
+ sourceCode.getLastToken(node),
+ util.NullThrowsReasons.MissingToken('closing parenthesis', node.type),
+ );
+ const openingParenToken = util.nullThrows(
+ sourceCode.getFirstTokenBetween(
+ node.callee,
+ closingParenToken,
+ isOpeningParenToken,
+ ),
+ util.NullThrowsReasons.MissingToken('opening parenthesis', node.type),
+ );
+
+ const argumentsText = sourceCode.text.substring(
+ openingParenToken.range[0],
+ closingParenToken.range[1],
+ );
+
+ return `${calleeText}${argumentsText}`;
+ }
+
+ if (node.type === AST_NODE_TYPES.Identifier) {
+ return node.name;
+ }
+
+ if (node.type === AST_NODE_TYPES.ThisExpression) {
+ return 'this';
+ }
+
+ return getMemberExpressionText(node);
+ }
+
+ /**
+ * Gets a normalised representation of the given MemberExpression
+ */
+ function getMemberExpressionText(
+ node: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression,
): string {
- const text = sourceCode.getText(
- node.type === AST_NODE_TYPES.BinaryExpression ? node.left : node,
- );
+ let objectText: string;
+
+ // cases should match the list in ALLOWED_MEMBER_OBJECT_TYPES
+ switch (node.object.type) {
+ case AST_NODE_TYPES.CallExpression:
+ case AST_NODE_TYPES.OptionalCallExpression:
+ case AST_NODE_TYPES.Identifier:
+ objectText = getText(node.object);
+ break;
+
+ case AST_NODE_TYPES.MemberExpression:
+ case AST_NODE_TYPES.OptionalMemberExpression:
+ objectText = getMemberExpressionText(node.object);
+ break;
+
+ case AST_NODE_TYPES.ThisExpression:
+ objectText = getText(node.object);
+ break;
+
+ /* istanbul ignore next */
+ default:
+ throw new Error(`Unexpected member object type: ${node.object.type}`);
+ }
+
+ let propertyText: string;
+ if (node.computed) {
+ // cases should match the list in ALLOWED_COMPUTED_PROP_TYPES
+ switch (node.property.type) {
+ case AST_NODE_TYPES.Identifier:
+ propertyText = getText(node.property);
+ break;
- // Removes spaces from the source code for the given node
- return text.replace(WHITESPACE_REGEX, '');
+ case AST_NODE_TYPES.Literal:
+ case AST_NODE_TYPES.BigIntLiteral:
+ case AST_NODE_TYPES.TemplateLiteral:
+ propertyText = sourceCode.getText(node.property);
+ break;
+
+ case AST_NODE_TYPES.MemberExpression:
+ case AST_NODE_TYPES.OptionalMemberExpression:
+ propertyText = getMemberExpressionText(node.property);
+ break;
+
+ /* istanbul ignore next */
+ default:
+ throw new Error(
+ `Unexpected member property type: ${node.object.type}`,
+ );
+ }
+
+ return `${objectText}${node.optional ? '?.' : ''}[${propertyText}]`;
+ } else {
+ // cases should match the list in ALLOWED_NON_COMPUTED_PROP_TYPES
+ switch (node.property.type) {
+ case AST_NODE_TYPES.Identifier:
+ propertyText = getText(node.property);
+ break;
+
+ /* istanbul ignore next */
+ default:
+ throw new Error(
+ `Unexpected member property type: ${node.object.type}`,
+ );
+ }
+
+ return `${objectText}${node.optional ? '?.' : '.'}${propertyText}`;
+ }
}
},
});
+const ALLOWED_MEMBER_OBJECT_TYPES: ReadonlySet = new Set([
+ AST_NODE_TYPES.CallExpression,
+ AST_NODE_TYPES.Identifier,
+ AST_NODE_TYPES.MemberExpression,
+ AST_NODE_TYPES.OptionalCallExpression,
+ AST_NODE_TYPES.OptionalMemberExpression,
+ AST_NODE_TYPES.ThisExpression,
+]);
+const ALLOWED_COMPUTED_PROP_TYPES: ReadonlySet = new Set([
+ AST_NODE_TYPES.BigIntLiteral,
+ AST_NODE_TYPES.Identifier,
+ AST_NODE_TYPES.Literal,
+ AST_NODE_TYPES.MemberExpression,
+ AST_NODE_TYPES.OptionalMemberExpression,
+ AST_NODE_TYPES.TemplateLiteral,
+]);
+const ALLOWED_NON_COMPUTED_PROP_TYPES: ReadonlySet = new Set([
+ AST_NODE_TYPES.Identifier,
+]);
+
function isValidChainTarget(
node: TSESTree.Node,
- allowIdentifier = false,
-): node is
- | TSESTree.BinaryExpression
- | TSESTree.CallExpression
- | TSESTree.MemberExpression {
+ allowIdentifier: boolean,
+): node is ValidChainTarget {
if (
node.type === AST_NODE_TYPES.MemberExpression ||
- node.type === AST_NODE_TYPES.CallExpression
+ node.type === AST_NODE_TYPES.OptionalMemberExpression
) {
- return true;
+ const isObjectValid =
+ ALLOWED_MEMBER_OBJECT_TYPES.has(node.object.type) &&
+ // make sure to validate the expression is of our expected structure
+ isValidChainTarget(node.object, true);
+ const isPropertyValid = node.computed
+ ? ALLOWED_COMPUTED_PROP_TYPES.has(node.property.type) &&
+ // make sure to validate the member expression is of our expected structure
+ (node.property.type === AST_NODE_TYPES.MemberExpression ||
+ node.property.type === AST_NODE_TYPES.OptionalMemberExpression
+ ? isValidChainTarget(node.property, allowIdentifier)
+ : true)
+ : ALLOWED_NON_COMPUTED_PROP_TYPES.has(node.property.type);
+
+ return isObjectValid && isPropertyValid;
}
- if (allowIdentifier && node.type === AST_NODE_TYPES.Identifier) {
+
+ if (
+ node.type === AST_NODE_TYPES.CallExpression ||
+ node.type === AST_NODE_TYPES.OptionalCallExpression
+ ) {
+ return isValidChainTarget(node.callee, allowIdentifier);
+ }
+
+ if (
+ allowIdentifier &&
+ (node.type === AST_NODE_TYPES.Identifier ||
+ node.type === AST_NODE_TYPES.ThisExpression)
+ ) {
return true;
}
diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts
index b96c612fd7a7..6594007651cc 100644
--- a/packages/eslint-plugin/src/rules/return-await.ts
+++ b/packages/eslint-plugin/src/rules/return-await.ts
@@ -68,11 +68,7 @@ export default util.createRule({
}
const type = checker.getTypeAtLocation(child);
-
- const isThenable =
- tsutils.isTypeFlagSet(type, ts.TypeFlags.Any) ||
- tsutils.isTypeFlagSet(type, ts.TypeFlags.Unknown) ||
- tsutils.isThenableType(checker, expression, type);
+ const isThenable = tsutils.isThenableType(checker, expression, type);
if (!isAwait && !isThenable) {
return;
diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts
index a23865f4a17b..20ea4382002a 100644
--- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts
+++ b/packages/eslint-plugin/src/rules/space-before-function-paren.ts
@@ -108,14 +108,14 @@ export default util.createRule({
node.async &&
isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 })!)
) {
- return overrideConfig.asyncArrow || baseConfig;
+ return overrideConfig.asyncArrow ?? baseConfig;
}
} else if (isNamedFunction(node)) {
- return overrideConfig.named || baseConfig;
+ return overrideConfig.named ?? baseConfig;
// `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}`
} else if (!node.generator) {
- return overrideConfig.anonymous || baseConfig;
+ return overrideConfig.anonymous ?? baseConfig;
}
return 'ignore';
diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts
index e67e4d1f687f..1b0d0de9462c 100644
--- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts
+++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts
@@ -150,7 +150,7 @@ export default util.createRule({
ForStatement: assertTestExpressionContainsBoolean,
IfStatement: assertTestExpressionContainsBoolean,
WhileStatement: assertTestExpressionContainsBoolean,
- LogicalExpression: assertLocalExpressionContainsBoolean,
+ 'LogicalExpression[operator!="??"]': assertLocalExpressionContainsBoolean,
'UnaryExpression[operator="!"]': assertUnaryExpressionContainsBoolean,
};
},
diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts
index d0f1c5b45a44..7c9162a0d05f 100644
--- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts
+++ b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts
@@ -76,7 +76,7 @@ export default util.createRule({
const punctuators = [':', '=>'];
const sourceCode = context.getSourceCode();
- const overrides = options!.overrides || { colon: {}, arrow: {} };
+ const overrides = options?.overrides ?? { colon: {}, arrow: {} };
const colonOptions = Object.assign(
{},
diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts
index c704d15b7ef2..e90490427c6c 100644
--- a/packages/eslint-plugin/src/rules/unified-signatures.ts
+++ b/packages/eslint-plugin/src/rules/unified-signatures.ts
@@ -500,7 +500,7 @@ export default util.createRule({
key?: string,
containingNode?: ContainingNode,
): void {
- key = key || getOverloadKey(signature);
+ key = key ?? getOverloadKey(signature);
if (
currentScope &&
(containingNode || signature).parent === currentScope.parent
diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts
index fccbafeede78..30e8231f95e2 100644
--- a/packages/eslint-plugin/src/util/astUtils.ts
+++ b/packages/eslint-plugin/src/util/astUtils.ts
@@ -17,6 +17,17 @@ function isNotOptionalChainPunctuator(
return !isOptionalChainPunctuator(token);
}
+function isNonNullAssertionPunctuator(
+ token: TSESTree.Token | TSESTree.Comment,
+): boolean {
+ return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '!';
+}
+function isNotNonNullAssertionPunctuator(
+ token: TSESTree.Token | TSESTree.Comment,
+): boolean {
+ return !isNonNullAssertionPunctuator(token);
+}
+
/**
* Returns true if and only if the node represents: foo?.() or foo.bar?.()
*/
@@ -32,8 +43,10 @@ function isOptionalOptionalChain(
}
export {
- LINEBREAK_MATCHER,
+ isNonNullAssertionPunctuator,
+ isNotNonNullAssertionPunctuator,
isNotOptionalChainPunctuator,
isOptionalChainPunctuator,
isOptionalOptionalChain,
+ LINEBREAK_MATCHER,
};
diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts
index b1aae71b3571..cb0430c114d7 100644
--- a/packages/eslint-plugin/src/util/index.ts
+++ b/packages/eslint-plugin/src/util/index.ts
@@ -4,6 +4,7 @@ export * from './astUtils';
export * from './createRule';
export * from './getParserServices';
export * from './misc';
+export * from './nullThrows';
export * from './types';
// this is done for convenience - saves migrating all of the old rules
diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts
index 58c41249d473..f7c8c0194ba5 100644
--- a/packages/eslint-plugin/src/util/misc.ts
+++ b/packages/eslint-plugin/src/util/misc.ts
@@ -11,14 +11,14 @@ import {
/**
* Check if the context file name is *.d.ts or *.d.tsx
*/
-export function isDefinitionFile(fileName: string): boolean {
+function isDefinitionFile(fileName: string): boolean {
return /\.d\.tsx?$/i.test(fileName || '');
}
/**
* Upper cases the first character or the string
*/
-export function upperCaseFirst(str: string): string {
+function upperCaseFirst(str: string): string {
return str[0].toUpperCase() + str.slice(1);
}
@@ -31,7 +31,7 @@ type InferOptionsTypeFromRuleNever = T extends TSESLint.RuleModule<
/**
* Uses type inference to fetch the TOptions type from the given RuleModule
*/
-export type InferOptionsTypeFromRule = T extends TSESLint.RuleModule<
+type InferOptionsTypeFromRule = T extends TSESLint.RuleModule<
string,
infer TOptions
>
@@ -41,7 +41,7 @@ export type InferOptionsTypeFromRule = T extends TSESLint.RuleModule<
/**
* Uses type inference to fetch the TMessageIds type from the given RuleModule
*/
-export type InferMessageIdsTypeFromRule = T extends TSESLint.RuleModule<
+type InferMessageIdsTypeFromRule = T extends TSESLint.RuleModule<
infer TMessageIds,
unknown[]
>
@@ -51,9 +51,7 @@ export type InferMessageIdsTypeFromRule = T extends TSESLint.RuleModule<
/**
* Gets a string name representation of the given PropertyName node
*/
-export function getNameFromPropertyName(
- propertyName: TSESTree.PropertyName,
-): string {
+function getNameFromPropertyName(propertyName: TSESTree.PropertyName): string {
if (propertyName.type === AST_NODE_TYPES.Identifier) {
return propertyName.name;
}
@@ -61,9 +59,9 @@ export function getNameFromPropertyName(
}
/** Return true if both parameters are equal. */
-export type Equal = (a: T, b: T) => boolean;
+type Equal = (a: T, b: T) => boolean;
-export function arraysAreEqual(
+function arraysAreEqual(
a: T[] | undefined,
b: T[] | undefined,
eq: (a: T, b: T) => boolean,
@@ -78,7 +76,7 @@ export function arraysAreEqual(
}
/** Returns the first non-`undefined` result. */
-export function findFirstResult(
+function findFirstResult(
inputs: T[],
getResult: (t: T) => U | undefined,
): U | undefined {
@@ -95,7 +93,7 @@ export function findFirstResult(
* Gets a string name representation of the name of the given MethodDefinition
* or ClassProperty node, with handling for computed property names.
*/
-export function getNameFromClassMember(
+function getNameFromClassMember(
methodDefinition:
| TSESTree.MethodDefinition
| TSESTree.ClassProperty
@@ -122,11 +120,25 @@ function keyCanBeReadAsPropertyName(
);
}
-export type ExcludeKeys<
+type ExcludeKeys<
TObj extends Record,
TKeys extends keyof TObj
> = { [k in Exclude]: TObj[k] };
-export type RequireKeys<
+type RequireKeys<
TObj extends Record,
TKeys extends keyof TObj
> = ExcludeKeys & { [k in TKeys]-?: Exclude };
+
+export {
+ arraysAreEqual,
+ Equal,
+ ExcludeKeys,
+ findFirstResult,
+ getNameFromClassMember,
+ getNameFromPropertyName,
+ InferMessageIdsTypeFromRule,
+ InferOptionsTypeFromRule,
+ isDefinitionFile,
+ RequireKeys,
+ upperCaseFirst,
+};
diff --git a/packages/eslint-plugin/src/util/nullThrows.ts b/packages/eslint-plugin/src/util/nullThrows.ts
new file mode 100644
index 000000000000..df644c2befb0
--- /dev/null
+++ b/packages/eslint-plugin/src/util/nullThrows.ts
@@ -0,0 +1,28 @@
+/**
+ * A set of common reasons for calling nullThrows
+ */
+const NullThrowsReasons = {
+ MissingParent: 'Expected node to have a parent.',
+ MissingToken: (token: string, thing: string) =>
+ `Expected to find a ${token} for the ${thing}.`,
+} as const;
+
+/**
+ * Assert that a value must not be null or undefined.
+ * This is a nice explicit alternative to the non-null assertion operator.
+ */
+function nullThrows(value: T | null | undefined, message: string): T {
+ // this function is primarily used to keep types happy in a safe way
+ // i.e. is used when we expect that a value is never nullish
+ // this means that it's pretty much impossible to test the below if...
+
+ // so ignore it in coverage metrics.
+ /* istanbul ignore if */
+ if (value === null || value === undefined) {
+ throw new Error(`Non-null Assertion Failed: ${message}`);
+ }
+
+ return value;
+}
+
+export { nullThrows, NullThrowsReasons };
diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts
index 2d0abcf963eb..6a3644acbb54 100644
--- a/packages/eslint-plugin/src/util/types.ts
+++ b/packages/eslint-plugin/src/util/types.ts
@@ -111,7 +111,7 @@ export function getConstrainedTypeAtLocation(
const nodeType = checker.getTypeAtLocation(node);
const constrained = checker.getBaseConstraintOfType(nodeType);
- return constrained || nodeType;
+ return constrained ?? nodeType;
}
/**
diff --git a/packages/eslint-plugin/tests/rules/no-empty-function.test.ts b/packages/eslint-plugin/tests/rules/no-empty-function.test.ts
index 4b40c1708676..9b5ad3507d3a 100644
--- a/packages/eslint-plugin/tests/rules/no-empty-function.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-empty-function.test.ts
@@ -32,6 +32,29 @@ ruleTester.run('no-empty-function', rule, {
}`,
options: [{ allow: ['methods'] }],
},
+ {
+ code: `
+class Foo {
+ private constructor() {}
+}
+ `,
+ options: [{ allow: ['private-constructors'] }],
+ },
+ {
+ code: `
+class Foo {
+ protected constructor() {}
+}
+ `,
+ options: [{ allow: ['protected-constructors'] }],
+ },
+ {
+ code: `
+function foo() {
+ const a = null;
+}
+ `,
+ },
],
invalid: [
@@ -65,5 +88,55 @@ ruleTester.run('no-empty-function', rule, {
},
],
},
+ {
+ code: `
+class Foo {
+ private constructor() {}
+}
+ `,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: {
+ name: 'constructor',
+ },
+ line: 3,
+ column: 25,
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ protected constructor() {}
+}
+ `,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: {
+ name: 'constructor',
+ },
+ line: 3,
+ column: 27,
+ },
+ ],
+ },
+ {
+ code: `
+function foo() {
+}
+ `,
+ errors: [
+ {
+ messageId: 'unexpected',
+ data: {
+ name: "function 'foo'",
+ },
+ line: 2,
+ column: 16,
+ },
+ ],
+ },
],
});
diff --git a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts
index 0bc8e5f3438f..d065c4109877 100644
--- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts
@@ -1009,7 +1009,7 @@ const test = >() => {};
suggestions: e.suggestions ?? suggestions(testCase.code),
})),
});
- const options = testCase.options || [];
+ const options = testCase.options ?? [];
const code = `// fixToUnknown: true\n${testCase.code}`;
acc.push({
code,
diff --git a/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts
index ac2287a85659..3bced219288a 100644
--- a/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-non-null-assertion.test.ts
@@ -6,15 +6,289 @@ const ruleTester = new RuleTester({
});
ruleTester.run('no-non-null-assertion', rule, {
- valid: ['const x = { y: 1 }; x.y;'],
+ valid: [
+ //
+ 'x',
+ 'x.y',
+ 'x.y.z',
+ 'x?.y.z',
+ 'x?.y?.z',
+ '!x',
+ ],
invalid: [
{
- code: 'const x = null; x!.y;',
+ code: 'x!',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: undefined,
+ },
+ ],
+ },
+ {
+ code: 'x!.y',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x?.y',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x.y!',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: undefined,
+ },
+ ],
+ },
+ {
+ code: '!x!.y',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 2,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: '!x?.y',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x!.y?.z',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x?.y?.z',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x![y]',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x?.[y]',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x![y]?.z',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x?.[y]?.z',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x.y.z!()',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x.y.z?.()',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x.y?.z!()',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x.y?.z?.()',
+ },
+ ],
+ },
+ ],
+ },
+ // some weirder cases that are stupid but valid
+ {
+ code: 'x!!!',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 5,
+ suggestions: undefined,
+ },
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 4,
+ suggestions: undefined,
+ },
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 3,
+ suggestions: undefined,
+ },
+ ],
+ },
+ {
+ code: 'x!!.y',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 4,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x!?.y',
+ },
+ ],
+ },
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 3,
+ suggestions: undefined,
+ },
+ ],
+ },
+ {
+ code: 'x.y!!',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 6,
+ suggestions: undefined,
+ },
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 5,
+ suggestions: undefined,
+ },
+ ],
+ },
+ {
+ code: 'x.y.z!!()',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 8,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x.y.z!?.()',
+ },
+ ],
+ },
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ endColumn: 7,
+ suggestions: undefined,
+ },
+ ],
+ },
+ {
+ code: 'x!?.[y].z',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x?.[y].z',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x!?.y.z',
+ errors: [
+ {
+ messageId: 'noNonNull',
+ line: 1,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x?.y.z',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ code: 'x.y.z!?.()',
errors: [
{
messageId: 'noNonNull',
line: 1,
- column: 17,
+ column: 1,
+ suggestions: [
+ {
+ messageId: 'suggestOptionalChain',
+ output: 'x.y.z?.()',
+ },
+ ],
},
],
},
diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts
index 30e511ac119d..0fe1326811c0 100644
--- a/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts
@@ -88,7 +88,23 @@ function test(t: T | []) {
function test(a: string) {
return a === "a"
}`,
-
+ // Nullish coalescing operator
+ `
+function test(a: string | null) {
+ return a ?? "default";
+}`,
+ `
+function test(a: string | undefined) {
+ return a ?? "default";
+}`,
+ `
+function test(a: string | null | undefined) {
+ return a ?? "default";
+}`,
+ `
+function test(a: unknown) {
+ return a ?? "default";
+}`,
// Supports ignoring the RHS
{
code: `
@@ -187,6 +203,35 @@ if (x === Foo.a) {}
`,
errors: [ruleError(8, 5, 'literalBooleanExpression')],
},
+ // Nullish coalescing operator
+ {
+ code: `
+function test(a: string) {
+ return a ?? 'default';
+}`,
+ errors: [ruleError(3, 10, 'neverNullish')],
+ },
+ {
+ code: `
+function test(a: string | false) {
+ return a ?? 'default';
+}`,
+ errors: [ruleError(3, 10, 'neverNullish')],
+ },
+ {
+ code: `
+function test(a: null) {
+ return a ?? 'default';
+}`,
+ errors: [ruleError(3, 10, 'alwaysNullish')],
+ },
+ {
+ code: `
+function test(a: never) {
+ return a ?? 'default';
+}`,
+ errors: [ruleError(3, 10, 'never')],
+ },
// Still errors on in the expected locations when ignoring RHS
{
diff --git a/packages/eslint-plugin/tests/rules/no-untyped-public-signature.test.ts b/packages/eslint-plugin/tests/rules/no-untyped-public-signature.test.ts
index 164f11b4bd76..28ee928cb41c 100644
--- a/packages/eslint-plugin/tests/rules/no-untyped-public-signature.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-untyped-public-signature.test.ts
@@ -99,6 +99,34 @@ class Foo {
`
class Foo {
abstract constructor(c: string) {}
+}
+ `,
+
+ // https://github.com/typescript-eslint/typescript-eslint/issues/1263
+ `
+class Foo {
+ private _x: string;
+
+ public get x(): string {
+ return this._x;
+ }
+
+ public set x(x: string) {
+ this._x = x;
+ }
+}
+ `,
+ `
+class Foo {
+ private _x: string;
+
+ get x(): string {
+ return this._x;
+ }
+
+ set x(x: string) {
+ this._x = x;
+ }
}
`,
],
@@ -240,6 +268,40 @@ class Foo {
code: `
class Foo {
abstract constructor(c) {}
+}
+ `,
+ errors: [{ messageId: 'untypedParameter' }],
+ },
+
+ // https://github.com/typescript-eslint/typescript-eslint/issues/1263
+ {
+ code: `
+class Foo {
+ private _x: string;
+
+ public get x(): string {
+ return this._x;
+ }
+
+ public set x(x) {
+ this._x = x;
+ }
+}
+ `,
+ errors: [{ messageId: 'untypedParameter' }],
+ },
+ {
+ code: `
+class Foo {
+ private _x: string;
+
+ get x(): string {
+ return this._x;
+ }
+
+ set x(x) {
+ this._x = x;
+ }
}
`,
errors: [{ messageId: 'untypedParameter' }],
diff --git a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts
index 46653b30c663..feb3a584bcc3 100644
--- a/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unused-expressions.test.ts
@@ -1,3 +1,4 @@
+import { TSESLint } from '@typescript-eslint/experimental-utils';
import rule from '../../src/rules/no-unused-expressions';
import { RuleTester } from '../RuleTester';
@@ -10,9 +11,11 @@ const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
});
+type TestCaseError = Omit, 'messageId'>;
+
// the base rule doesn't have messageIds
function error(
- messages: { line: number; column: number }[],
+ messages: TestCaseError[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): any[] {
return messages.map(message => ({
@@ -42,6 +45,26 @@ ruleTester.run('no-unused-expressions', rule, {
`
a?.['b']?.c();
`,
+ `
+ module Foo {
+ 'use strict';
+ }
+ `,
+ `
+ namespace Foo {
+ 'use strict';
+
+ export class Foo {}
+ export class Bar {}
+ }
+ `,
+ `
+ function foo() {
+ 'use strict';
+
+ return null;
+ }
+ `,
],
invalid: [
{
@@ -176,5 +199,56 @@ one.two?.three.four;
},
]),
},
+ {
+ code: `
+module Foo {
+ const foo = true;
+ 'use strict';
+}
+ `,
+ errors: error([
+ {
+ line: 4,
+ endLine: 4,
+ column: 3,
+ endColumn: 16,
+ },
+ ]),
+ },
+ {
+ code: `
+namespace Foo {
+ export class Foo {}
+ export class Bar {}
+
+ 'use strict';
+}
+ `,
+ errors: error([
+ {
+ line: 6,
+ endLine: 6,
+ column: 3,
+ endColumn: 16,
+ },
+ ]),
+ },
+ {
+ code: `
+function foo() {
+ const foo = true;
+
+ 'use strict';
+}
+ `,
+ errors: error([
+ {
+ line: 5,
+ endLine: 5,
+ column: 3,
+ endColumn: 16,
+ },
+ ]),
+ },
],
});
diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts
index 1f05d6b9a3fc..7dd9212770c6 100644
--- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts
+++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts
@@ -435,5 +435,33 @@ if (function werid() { return x ?? 'foo' }) {}
},
],
})),
+
+ // testing the suggestion fixer option
+ {
+ code: `
+declare const x: string | null;
+x || 'foo';
+ `.trimRight(),
+ output: null,
+ options: [{ forceSuggestionFixer: true }],
+ errors: [
+ {
+ messageId: 'preferNullish',
+ line: 3,
+ column: 3,
+ endLine: 3,
+ endColumn: 5,
+ suggestions: [
+ {
+ messageId: 'preferNullish',
+ output: `
+declare const x: string | null;
+x ?? 'foo';
+ `.trimRight(),
+ },
+ ],
+ },
+ ],
+ },
],
});
diff --git a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts
index 2cadf43883e7..ccd3e992cf01 100644
--- a/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts
+++ b/packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts
@@ -13,136 +13,93 @@ const ruleTester = new RuleTester({
const baseCases = [
// chained members
{
- code: `
- foo && foo.bar
- `,
- output: `
- foo?.bar
- `,
+ code: 'foo && foo.bar',
+ output: 'foo?.bar',
},
{
- code: `
- foo && foo()
- `,
- output: `
- foo?.()
- `,
+ code: 'foo && foo()',
+ output: 'foo?.()',
},
{
- code: `
- foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz
- `,
- output: `
- foo?.bar?.baz?.buzz
- `,
+ code: 'foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz',
+ output: 'foo?.bar?.baz?.buzz',
},
{
// case with a jump (i.e. a non-nullish prop)
- code: `
- foo && foo.bar && foo.bar.baz.buzz
- `,
- output: `
- foo?.bar?.baz.buzz
- `,
+ code: 'foo && foo.bar && foo.bar.baz.buzz',
+ output: 'foo?.bar?.baz.buzz',
},
{
// case where for some reason there is a doubled up expression
- code: `
- foo && foo.bar && foo.bar.baz && foo.bar.baz && foo.bar.baz.buzz
- `,
- output: `
- foo?.bar?.baz?.buzz
- `,
+ code: 'foo && foo.bar && foo.bar.baz && foo.bar.baz && foo.bar.baz.buzz',
+ output: 'foo?.bar?.baz?.buzz',
},
// chained members with element access
{
- code: `
- foo && foo[bar] && foo[bar].baz && foo[bar].baz.buzz
- `,
- output: `
- foo?.[bar]?.baz?.buzz
- `,
+ code: 'foo && foo[bar] && foo[bar].baz && foo[bar].baz.buzz',
+ output: 'foo?.[bar]?.baz?.buzz',
},
{
// case with a jump (i.e. a non-nullish prop)
- code: `
- foo && foo[bar].baz && foo[bar].baz.buzz
- `,
- output: `
- foo?.[bar].baz?.buzz
- `,
+ code: 'foo && foo[bar].baz && foo[bar].baz.buzz',
+ output: 'foo?.[bar].baz?.buzz',
},
// chained calls
{
- code: `
- foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz()
- `,
- output: `
- foo?.bar?.baz?.buzz()
- `,
+ code: 'foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz()',
+ output: 'foo?.bar?.baz?.buzz()',
},
{
- code: `
- foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz && foo.bar.baz.buzz()
- `,
- output: `
- foo?.bar?.baz?.buzz?.()
- `,
+ code:
+ 'foo && foo.bar && foo.bar.baz && foo.bar.baz.buzz && foo.bar.baz.buzz()',
+ output: 'foo?.bar?.baz?.buzz?.()',
},
{
// case with a jump (i.e. a non-nullish prop)
- code: `
- foo && foo.bar && foo.bar.baz.buzz()
- `,
- output: `
- foo?.bar?.baz.buzz()
- `,
+ code: 'foo && foo.bar && foo.bar.baz.buzz()',
+ output: 'foo?.bar?.baz.buzz()',
},
{
// case with a jump (i.e. a non-nullish prop)
- code: `
- foo && foo.bar && foo.bar.baz.buzz && foo.bar.baz.buzz()
- `,
- output: `
- foo?.bar?.baz.buzz?.()
- `,
+ code: 'foo && foo.bar && foo.bar.baz.buzz && foo.bar.baz.buzz()',
+ output: 'foo?.bar?.baz.buzz?.()',
},
{
// case with a call expr inside the chain for some inefficient reason
- code: `
- foo && foo.bar() && foo.bar().baz && foo.bar().baz.buzz && foo.bar().baz.buzz()
- `,
- output: `
- foo?.bar()?.baz?.buzz?.()
- `,
+ code:
+ 'foo && foo.bar() && foo.bar().baz && foo.bar().baz.buzz && foo.bar().baz.buzz()',
+ output: 'foo?.bar()?.baz?.buzz?.()',
},
// chained calls with element access
{
- code: `
- foo && foo.bar && foo.bar.baz && foo.bar.baz[buzz]()
- `,
- output: `
- foo?.bar?.baz?.[buzz]()
- `,
+ code: 'foo && foo.bar && foo.bar.baz && foo.bar.baz[buzz]()',
+ output: 'foo?.bar?.baz?.[buzz]()',
},
{
- code: `
- foo && foo.bar && foo.bar.baz && foo.bar.baz[buzz] && foo.bar.baz[buzz]()
- `,
- output: `
- foo?.bar?.baz?.[buzz]?.()
- `,
+ code:
+ 'foo && foo.bar && foo.bar.baz && foo.bar.baz[buzz] && foo.bar.baz[buzz]()',
+ output: 'foo?.bar?.baz?.[buzz]?.()',
},
// two-for-one
{
- code: `
- foo && foo.bar && foo.bar.baz || baz && baz.bar && baz.bar.foo
- `,
- output: `
- foo?.bar?.baz || baz?.bar?.foo
- `,
+ code: 'foo && foo.bar && foo.bar.baz || baz && baz.bar && baz.bar.foo',
+ output: 'foo?.bar?.baz || baz?.bar?.foo',
errors: 2,
},
+ // (partially) pre-optional chained
+ {
+ code:
+ 'foo && foo?.bar && foo?.bar.baz && foo?.bar.baz[buzz] && foo?.bar.baz[buzz]()',
+ output: 'foo?.bar?.baz?.[buzz]?.()',
+ },
+ {
+ code: 'foo && foo?.bar.baz && foo?.bar.baz[buzz]',
+ output: 'foo?.bar.baz?.[buzz]',
+ },
+ {
+ code: 'foo && foo?.() && foo?.().bar',
+ output: 'foo?.()?.bar',
+ },
].map(
c =>
({
@@ -167,6 +124,15 @@ ruleTester.run('prefer-optional-chain', rule, {
'foo ?? foo.bar',
"file !== 'index.ts' && file.endsWith('.ts')",
'nextToken && sourceCode.isSpaceBetweenTokens(prevToken, nextToken)',
+ 'result && this.options.shouldPreserveNodeMaps',
+ 'foo && fooBar.baz',
+ 'match && match$1 !== undefined',
+ 'foo !== null && foo !== undefined',
+ 'x["y"] !== undefined && x["y"] !== null',
+ // currently do not handle complex computed properties
+ 'foo && foo[bar as string] && foo[bar as string].baz',
+ 'foo && foo[1 + 2] && foo[1 + 2].baz',
+ 'foo && foo[typeof bar] && foo[typeof bar].baz',
],
invalid: [
...baseCases,
@@ -193,27 +159,93 @@ ruleTester.run('prefer-optional-chain', rule, {
// strict nullish equality checks x !== null && x.y !== null
...baseCases.map(c => ({
...c,
- code: c.code.replace(/&&/g, ' !== null &&'),
+ code: c.code.replace(/&&/g, '!== null &&'),
})),
...baseCases.map(c => ({
...c,
- code: c.code.replace(/&&/g, ' != null &&'),
+ code: c.code.replace(/&&/g, '!= null &&'),
})),
...baseCases.map(c => ({
...c,
- code: c.code.replace(/&&/g, ' !== undefined &&'),
+ code: c.code.replace(/&&/g, '!== undefined &&'),
})),
...baseCases.map(c => ({
...c,
- code: c.code.replace(/&&/g, ' != undefined &&'),
+ code: c.code.replace(/&&/g, '!= undefined &&'),
})),
{
// case with inconsistent checks
+ code:
+ 'foo && foo.bar != null && foo.bar.baz !== undefined && foo.bar.baz.buzz',
+ output: 'foo?.bar?.baz?.buzz',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ // ensure essential whitespace isn't removed
+ {
+ code: 'foo && foo.bar(baz => ());',
+ output: 'foo?.bar(baz => ());',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ {
+ code: 'foo && foo.bar(baz => typeof baz);',
+ output: 'foo?.bar(baz => typeof baz);',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ {
+ code: 'foo && foo["some long string"] && foo["some long string"].baz',
+ output: 'foo?.["some long string"]?.baz',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ {
+ code: 'foo && foo[`some long string`] && foo[`some long string`].baz',
+ output: 'foo?.[`some long string`]?.baz',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ {
+ code: "foo && foo['some long string'] && foo['some long string'].baz",
+ output: "foo?.['some long string']?.baz",
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ // should preserve comments in a call expression
+ {
code: `
- foo && foo.bar != null && foo.bar.baz && foo.bar.baz.buzz !== undefined
+ foo && foo.bar(/* comment */a,
+ // comment2
+ b, );
`,
output: `
- foo?.bar?.baz?.buzz
+ foo?.bar(/* comment */a,
+ // comment2
+ b, );
`,
errors: [
{
@@ -221,5 +253,43 @@ ruleTester.run('prefer-optional-chain', rule, {
},
],
},
+ // ensure binary expressions that are the last expression do not get removed
+ {
+ code: 'foo && foo.bar != null',
+ output: 'foo?.bar != null',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ {
+ code: 'foo && foo.bar != undefined',
+ output: 'foo?.bar != undefined',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ {
+ code: 'foo && foo.bar != null && baz',
+ output: 'foo?.bar != null && baz',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
+ // other weird cases
+ {
+ code: 'foo && foo?.()',
+ output: 'foo?.()',
+ errors: [
+ {
+ messageId: 'preferOptionalChain',
+ },
+ ],
+ },
],
});
diff --git a/packages/eslint-plugin/tests/rules/return-await.test.ts b/packages/eslint-plugin/tests/rules/return-await.test.ts
index bddbc85d22c0..fc5f2597ebf4 100644
--- a/packages/eslint-plugin/tests/rules/return-await.test.ts
+++ b/packages/eslint-plugin/tests/rules/return-await.test.ts
@@ -148,6 +148,16 @@ ruleTester.run('return-await', rule, {
}
}`,
},
+ {
+ code: `async function test(): Promise {
+ const res = await Promise.resolve('{}');
+ try {
+ return JSON.parse(res);
+ } catch (error) {
+ return res;
+ }
+ }`,
+ },
],
invalid: [
{
diff --git a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts
index c95228b67430..f1b98a1c9411 100644
--- a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts
+++ b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts
@@ -159,11 +159,11 @@ ruleTester.run('strict-boolean-expressions', rule, {
{
options: [{ ignoreRhs: true }],
code: `
-const obj = {};
-const bool = false;
-const boolOrObj = bool || obj;
-const boolAndObj = bool && obj;
-`,
+ const obj = {};
+ const bool = false;
+ const boolOrObj = bool || obj;
+ const boolAndObj = bool && obj;
+ `,
},
{
options: [{ allowNullable: true }],
@@ -174,6 +174,10 @@ const boolAndObj = bool && obj;
const f4 = (x?: false) => x ? 1 : 0;
`,
},
+ `
+ declare const x: string | null;
+ y = x ?? 'foo';
+ `,
],
invalid: [
diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md
index fcacc1bd5071..236368fb39cc 100644
--- a/packages/experimental-utils/CHANGELOG.md
+++ b/packages/experimental-utils/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+
+### Features
+
+* **eslint-plugin:** [no-non-null-assert] add suggestion fixer ([#1260](https://github.com/typescript-eslint/typescript-eslint/issues/1260)) ([e350a21](https://github.com/typescript-eslint/typescript-eslint/commit/e350a21))
+* **experimental-utils:** add isSpaceBetween declaration to Sou… ([#1268](https://github.com/typescript-eslint/typescript-eslint/issues/1268)) ([f83f04b](https://github.com/typescript-eslint/typescript-eslint/commit/f83f04b))
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json
index 5960b669d597..310714e14f53 100644
--- a/packages/experimental-utils/package.json
+++ b/packages/experimental-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@typescript-eslint/experimental-utils",
- "version": "2.9.0",
+ "version": "2.10.0",
"description": "(Experimental) Utilities for working with TypeScript + ESLint together",
"keywords": [
"eslint",
@@ -37,7 +37,7 @@
},
"dependencies": {
"@types/json-schema": "^7.0.3",
- "@typescript-eslint/typescript-estree": "2.9.0",
+ "@typescript-eslint/typescript-estree": "2.10.0",
"eslint-scope": "^5.0.0"
},
"peerDependencies": {
diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts
index c74358da5587..fcc0e11ae701 100644
--- a/packages/experimental-utils/src/ts-eslint/Rule.ts
+++ b/packages/experimental-utils/src/ts-eslint/Rule.ts
@@ -105,9 +105,9 @@ interface RuleFixer {
type ReportFixFunction = (
fixer: RuleFixer,
) => null | RuleFix | RuleFix[] | IterableIterator;
-type ReportSuggestionArray = Readonly<
- ReportDescriptorBase[]
->;
+type ReportSuggestionArray = ReportDescriptorBase<
+ TMessageIds
+>[];
interface ReportDescriptorBase {
/**
@@ -132,7 +132,7 @@ interface ReportDescriptorWithSuggestion
/**
* 6.7's Suggestions API
*/
- suggest?: ReportSuggestionArray | null;
+ suggest?: Readonly> | null;
}
interface ReportDescriptorNodeOptionalLoc {
diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts
index c59574589b45..ce3fa87096f4 100644
--- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts
+++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts
@@ -45,7 +45,7 @@ interface TestCaseError {
column?: number;
endLine?: number;
endColumn?: number;
- suggestions?: SuggestionOutput[];
+ suggestions?: SuggestionOutput[] | null;
}
interface RunTests<
diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts
index ac8331166063..6744e988d8d4 100644
--- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts
+++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts
@@ -32,6 +32,14 @@ declare interface SourceCode {
getNodeByRangeIndex(index: number): TSESTree.Node | null;
+ isSpaceBetween(
+ first: TSESTree.Token | TSESTree.Node,
+ second: TSESTree.Token | TSESTree.Node,
+ ): boolean;
+
+ /**
+ * @deprecated in favor of isSpaceBetween()
+ */
isSpaceBetweenTokens(first: TSESTree.Token, second: TSESTree.Token): boolean;
getLocFromIndex(index: number): TSESTree.LineAndColumnData;
diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md
index 7153621c2f2f..7b665de26a05 100644
--- a/packages/parser/CHANGELOG.md
+++ b/packages/parser/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+**Note:** Version bump only for package @typescript-eslint/parser
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
**Note:** Version bump only for package @typescript-eslint/parser
diff --git a/packages/parser/package.json b/packages/parser/package.json
index 41b4ee671951..8641c433f05f 100644
--- a/packages/parser/package.json
+++ b/packages/parser/package.json
@@ -1,6 +1,6 @@
{
"name": "@typescript-eslint/parser",
- "version": "2.9.0",
+ "version": "2.10.0",
"description": "An ESLint custom parser which leverages TypeScript ESTree",
"main": "dist/parser.js",
"types": "dist/parser.d.ts",
@@ -43,13 +43,13 @@
},
"dependencies": {
"@types/eslint-visitor-keys": "^1.0.0",
- "@typescript-eslint/experimental-utils": "2.9.0",
- "@typescript-eslint/typescript-estree": "2.9.0",
+ "@typescript-eslint/experimental-utils": "2.10.0",
+ "@typescript-eslint/typescript-estree": "2.10.0",
"eslint-visitor-keys": "^1.1.0"
},
"devDependencies": {
"@types/glob": "^7.1.1",
- "@typescript-eslint/shared-fixtures": "2.9.0",
+ "@typescript-eslint/shared-fixtures": "2.10.0",
"glob": "*"
}
}
diff --git a/packages/parser/src/analyze-scope.ts b/packages/parser/src/analyze-scope.ts
index db9088604340..25cc0dfa27ee 100644
--- a/packages/parser/src/analyze-scope.ts
+++ b/packages/parser/src/analyze-scope.ts
@@ -879,7 +879,7 @@ export function analyzeScope(
parserOptions.ecmaFeatures.globalReturn) === true,
impliedStrict: false,
sourceType: parserOptions.sourceType,
- ecmaVersion: parserOptions.ecmaVersion || 2018,
+ ecmaVersion: parserOptions.ecmaVersion ?? 2018,
childVisitorKeys,
fallback,
};
diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md
index 32efd0513156..cc0c786f2eab 100644
--- a/packages/shared-fixtures/CHANGELOG.md
+++ b/packages/shared-fixtures/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+**Note:** Version bump only for package @typescript-eslint/shared-fixtures
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
**Note:** Version bump only for package @typescript-eslint/shared-fixtures
diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json
index d7e303ae1a6b..8c2d9ed8c1ed 100644
--- a/packages/shared-fixtures/package.json
+++ b/packages/shared-fixtures/package.json
@@ -1,6 +1,6 @@
{
"name": "@typescript-eslint/shared-fixtures",
- "version": "2.9.0",
+ "version": "2.10.0",
"private": true,
"scripts": {
"build": "tsc -b tsconfig.build.json",
diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md
index 27973aa5246d..6a4367f1718f 100644
--- a/packages/typescript-estree/CHANGELOG.md
+++ b/packages/typescript-estree/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [2.10.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.9.0...v2.10.0) (2019-12-02)
+
+
+### Bug Fixes
+
+* **eslint-plugin:** [no-unused-expressions] ignore directives ([#1285](https://github.com/typescript-eslint/typescript-eslint/issues/1285)) ([ce4c803](https://github.com/typescript-eslint/typescript-eslint/commit/ce4c803))
+* **typescript-estree:** make FunctionDeclaration.body non-null ([#1288](https://github.com/typescript-eslint/typescript-eslint/issues/1288)) ([dc73510](https://github.com/typescript-eslint/typescript-eslint/commit/dc73510))
+
+
+
+
+
# [2.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.8.0...v2.9.0) (2019-11-25)
diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json
index 8a0857d19af5..ced5d06d426a 100644
--- a/packages/typescript-estree/package.json
+++ b/packages/typescript-estree/package.json
@@ -1,6 +1,6 @@
{
"name": "@typescript-eslint/typescript-estree",
- "version": "2.9.0",
+ "version": "2.10.0",
"description": "A parser that converts TypeScript source code into an ESTree compatible form",
"main": "dist/parser.js",
"types": "dist/parser.d.ts",
@@ -59,7 +59,7 @@
"@types/lodash.unescape": "^4.0.4",
"@types/semver": "^6.2.0",
"@types/tmp": "^0.1.0",
- "@typescript-eslint/shared-fixtures": "2.9.0",
+ "@typescript-eslint/shared-fixtures": "2.10.0",
"babel-code-frame": "^6.26.0",
"lodash.isplainobject": "4.0.6",
"tmp": "^0.1.0",
diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts
index c5dd74449551..25937fc89025 100644
--- a/packages/typescript-estree/src/convert.ts
+++ b/packages/typescript-estree/src/convert.ts
@@ -111,7 +111,7 @@ export class Converter {
this.allowPattern = allowPattern;
}
- const result = this.convertNode(node as TSNode, parent || node.parent);
+ const result = this.convertNode(node as TSNode, parent ?? node.parent);
this.registerTSNodeInNodeMap(node, result);
@@ -1193,12 +1193,12 @@ export class Converter {
if (node.dotDotDotToken) {
result = this.createNode(node, {
type: AST_NODE_TYPES.RestElement,
- argument: this.convertChild(node.propertyName || node.name),
+ argument: this.convertChild(node.propertyName ?? node.name),
});
} else {
result = this.createNode(node, {
type: AST_NODE_TYPES.Property,
- key: this.convertChild(node.propertyName || node.name),
+ key: this.convertChild(node.propertyName ?? node.name),
value: this.convertChild(node.name),
computed: Boolean(
node.propertyName &&
@@ -1387,7 +1387,7 @@ export class Converter {
if (node.modifiers) {
return this.createNode(node, {
type: AST_NODE_TYPES.TSParameterProperty,
- accessibility: getTSNodeAccessibility(node) || undefined,
+ accessibility: getTSNodeAccessibility(node) ?? undefined,
readonly:
hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined,
static: hasModifier(SyntaxKind.StaticKeyword, node) || undefined,
@@ -1402,7 +1402,7 @@ export class Converter {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression: {
- const heritageClauses = node.heritageClauses || [];
+ const heritageClauses = node.heritageClauses ?? [];
const classNodeType =
node.kind === SyntaxKind.ClassDeclaration
? AST_NODE_TYPES.ClassDeclaration
@@ -1533,7 +1533,7 @@ export class Converter {
return this.createNode(node, {
type: AST_NODE_TYPES.ImportSpecifier,
local: this.convertChild(node.name),
- imported: this.convertChild(node.propertyName || node.name),
+ imported: this.convertChild(node.propertyName ?? node.name),
});
case SyntaxKind.ImportClause:
@@ -1563,7 +1563,7 @@ export class Converter {
case SyntaxKind.ExportSpecifier:
return this.createNode(node, {
type: AST_NODE_TYPES.ExportSpecifier,
- local: this.convertChild(node.propertyName || node.name),
+ local: this.convertChild(node.propertyName ?? node.name),
exported: this.convertChild(node.name),
});
@@ -1584,7 +1584,7 @@ export class Converter {
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression: {
- const operator = (getTextForTokenKind(node.operator) || '') as any;
+ const operator = (getTextForTokenKind(node.operator) ?? '') as any;
/**
* ESTree uses UpdateExpression for ++/--
*/
@@ -2375,7 +2375,7 @@ export class Converter {
}
case SyntaxKind.InterfaceDeclaration: {
- const interfaceHeritageClauses = node.heritageClauses || [];
+ const interfaceHeritageClauses = node.heritageClauses ?? [];
const result = this.createNode(node, {
type: AST_NODE_TYPES.TSInterfaceDeclaration,
body: this.createNode(node, {
diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts
index 03624dfd2266..73dc8ec426e6 100644
--- a/packages/typescript-estree/src/create-program/createWatchProgram.ts
+++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts
@@ -174,7 +174,7 @@ function getProgramsForProjects(
log('Found existing program for file. %s', filePath);
updatedProgram =
- updatedProgram || existingWatch.getProgram().getProgram();
+ updatedProgram ?? existingWatch.getProgram().getProgram();
// sets parent pointers in source files
updatedProgram.getTypeChecker();
diff --git a/packages/typescript-estree/src/simple-traverse.ts b/packages/typescript-estree/src/simple-traverse.ts
index e21d24d0de8b..ab4a07937d09 100644
--- a/packages/typescript-estree/src/simple-traverse.ts
+++ b/packages/typescript-estree/src/simple-traverse.ts
@@ -11,7 +11,7 @@ function getVisitorKeysForNode(
node: TSESTree.Node,
): readonly string[] {
const keys = allVisitorKeys[node.type];
- return keys || [];
+ return keys ?? [];
}
interface SimpleTraverseOptions {
diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts
index afda76ead68d..33784097a01e 100644
--- a/packages/typescript-estree/src/ts-estree/ts-estree.ts
+++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts
@@ -691,6 +691,7 @@ export interface ExportSpecifier extends BaseNode {
export interface ExpressionStatement extends BaseNode {
type: AST_NODE_TYPES.ExpressionStatement;
expression: Expression;
+ directive?: string;
}
export interface ForInStatement extends BaseNode {
@@ -718,6 +719,7 @@ export interface ForStatement extends BaseNode {
export interface FunctionDeclaration extends FunctionDeclarationBase {
type: AST_NODE_TYPES.FunctionDeclaration;
+ body: BlockStatement;
}
export interface FunctionExpression extends FunctionDeclarationBase {
diff --git a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts
index 557ac0c11ce5..17aedafdc801 100644
--- a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts
+++ b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts
@@ -57,9 +57,9 @@ class FixturesTester {
}
}
- const ignore = config.ignore || [];
- const fileType = config.fileType || 'js';
- const ignoreSourceType = config.ignoreSourceType || [];
+ const ignore = config.ignore ?? [];
+ const fileType = config.fileType ?? 'js';
+ const ignoreSourceType = config.ignoreSourceType ?? [];
const jsx = isJSXFileType(fileType);
/**