Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e350a21

Browse files
authored
feat(eslint-plugin): [no-non-null-assert] add suggestion fixer (typescript-eslint#1260)
1 parent ce4c803 commit e350a21

File tree

5 files changed

+393
-10
lines changed

5 files changed

+393
-10
lines changed

packages/eslint-plugin/src/rules/no-non-null-assertion.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import {
2+
TSESLint,
3+
AST_NODE_TYPES,
4+
} from '@typescript-eslint/experimental-utils';
15
import * as util from '../util';
26

3-
export default util.createRule({
7+
type MessageIds = 'noNonNull' | 'suggestOptionalChain';
8+
9+
export default util.createRule<[], MessageIds>({
410
name: 'no-non-null-assertion',
511
meta: {
612
type: 'problem',
@@ -12,16 +18,106 @@ export default util.createRule({
1218
},
1319
messages: {
1420
noNonNull: 'Forbidden non-null assertion.',
21+
suggestOptionalChain:
22+
'Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator.',
1523
},
1624
schema: [],
1725
},
1826
defaultOptions: [],
1927
create(context) {
28+
const sourceCode = context.getSourceCode();
2029
return {
2130
TSNonNullExpression(node): void {
31+
const suggest: TSESLint.ReportSuggestionArray<MessageIds> = [];
32+
function convertTokenToOptional(
33+
replacement: '?' | '?.',
34+
): TSESLint.ReportFixFunction {
35+
return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => {
36+
const operator = sourceCode.getTokenAfter(
37+
node.expression,
38+
util.isNonNullAssertionPunctuator,
39+
);
40+
if (operator) {
41+
return fixer.replaceText(operator, replacement);
42+
}
43+
44+
return null;
45+
};
46+
}
47+
function removeToken(): TSESLint.ReportFixFunction {
48+
return (fixer: TSESLint.RuleFixer): TSESLint.RuleFix | null => {
49+
const operator = sourceCode.getTokenAfter(
50+
node.expression,
51+
util.isNonNullAssertionPunctuator,
52+
);
53+
if (operator) {
54+
return fixer.remove(operator);
55+
}
56+
57+
return null;
58+
};
59+
}
60+
61+
if (node.parent) {
62+
if (
63+
(node.parent.type === AST_NODE_TYPES.MemberExpression ||
64+
node.parent.type === AST_NODE_TYPES.OptionalMemberExpression) &&
65+
node.parent.object === node
66+
) {
67+
if (!node.parent.optional) {
68+
if (node.parent.computed) {
69+
// it is x![y]?.z
70+
suggest.push({
71+
messageId: 'suggestOptionalChain',
72+
fix: convertTokenToOptional('?.'),
73+
});
74+
} else {
75+
// it is x!.y?.z
76+
suggest.push({
77+
messageId: 'suggestOptionalChain',
78+
fix: convertTokenToOptional('?'),
79+
});
80+
}
81+
} else {
82+
if (node.parent.computed) {
83+
// it is x!?.[y].z
84+
suggest.push({
85+
messageId: 'suggestOptionalChain',
86+
fix: removeToken(),
87+
});
88+
} else {
89+
// it is x!?.y.z
90+
suggest.push({
91+
messageId: 'suggestOptionalChain',
92+
fix: removeToken(),
93+
});
94+
}
95+
}
96+
} else if (
97+
(node.parent.type === AST_NODE_TYPES.CallExpression ||
98+
node.parent.type === AST_NODE_TYPES.OptionalCallExpression) &&
99+
node.parent.callee === node
100+
) {
101+
if (!node.parent.optional) {
102+
// it is x.y?.z!()
103+
suggest.push({
104+
messageId: 'suggestOptionalChain',
105+
fix: convertTokenToOptional('?.'),
106+
});
107+
} else {
108+
// it is x.y.z!?.()
109+
suggest.push({
110+
messageId: 'suggestOptionalChain',
111+
fix: removeToken(),
112+
});
113+
}
114+
}
115+
}
116+
22117
context.report({
23118
node,
24119
messageId: 'noNonNull',
120+
suggest,
25121
});
26122
},
27123
};

packages/eslint-plugin/src/util/astUtils.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ function isNotOptionalChainPunctuator(
1717
return !isOptionalChainPunctuator(token);
1818
}
1919

20+
function isNonNullAssertionPunctuator(
21+
token: TSESTree.Token | TSESTree.Comment,
22+
): boolean {
23+
return token.type === AST_TOKEN_TYPES.Punctuator && token.value === '!';
24+
}
25+
function isNotNonNullAssertionPunctuator(
26+
token: TSESTree.Token | TSESTree.Comment,
27+
): boolean {
28+
return !isNonNullAssertionPunctuator(token);
29+
}
30+
2031
/**
2132
* Returns true if and only if the node represents: foo?.() or foo.bar?.()
2233
*/
@@ -32,8 +43,10 @@ function isOptionalOptionalChain(
3243
}
3344

3445
export {
35-
LINEBREAK_MATCHER,
46+
isNonNullAssertionPunctuator,
47+
isNotNonNullAssertionPunctuator,
3648
isNotOptionalChainPunctuator,
3749
isOptionalChainPunctuator,
3850
isOptionalOptionalChain,
51+
LINEBREAK_MATCHER,
3952
};

0 commit comments

Comments
 (0)