no-non-null-asserted-optional-chain.js
6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
const ts = __importStar(require("typescript"));
const semver = __importStar(require("semver"));
const util = __importStar(require("../util"));
const is3dot9 = semver.satisfies(ts.version, `>= 3.9.0 || >= 3.9.1-rc || >= 3.9.0-beta`, {
includePrerelease: true,
});
exports.default = util.createRule({
name: 'no-non-null-asserted-optional-chain',
meta: {
type: 'problem',
docs: {
description: 'Disallows using a non-null assertion after an optional chain expression',
category: 'Possible Errors',
recommended: 'error',
suggestion: true,
},
messages: {
noNonNullOptionalChain: 'Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong.',
suggestRemovingNonNull: 'You should remove the non-null assertion.',
},
schema: [],
},
defaultOptions: [],
create(context) {
// TS3.9 made a breaking change to how non-null works with optional chains.
// Pre-3.9, `x?.y!.z` means `(x?.y).z` - i.e. it essentially scrubbed the optionality from the chain
// Post-3.9, `x?.y!.z` means `x?.y!.z` - i.e. it just asserts that the property `y` is non-null, not the result of `x?.y`.
// This means that for > 3.9, x?.y!.z is valid!
//
// NOTE: these cases are still invalid for 3.9:
// - x?.y.z!
// - (x?.y)!.z
const baseSelectors = {
// non-nulling a wrapped chain will scrub all nulls introduced by the chain
// (x?.y)!
// (x?.())!
'TSNonNullExpression > ChainExpression'(node) {
// selector guarantees this assertion
const parent = node.parent;
context.report({
node,
messageId: 'noNonNullOptionalChain',
// use a suggestion instead of a fixer, because this can obviously break type checks
suggest: [
{
messageId: 'suggestRemovingNonNull',
fix(fixer) {
return fixer.removeRange([
parent.range[1] - 1,
parent.range[1],
]);
},
},
],
});
},
// non-nulling at the end of a chain will scrub all nulls introduced by the chain
// x?.y!
// x?.()!
'ChainExpression > TSNonNullExpression'(node) {
context.report({
node,
messageId: 'noNonNullOptionalChain',
// use a suggestion instead of a fixer, because this can obviously break type checks
suggest: [
{
messageId: 'suggestRemovingNonNull',
fix(fixer) {
return fixer.removeRange([node.range[1] - 1, node.range[1]]);
},
},
],
});
},
};
if (is3dot9) {
return baseSelectors;
}
return Object.assign(Object.assign({}, baseSelectors), { [[
// > :not(ChainExpression) because that case is handled by a previous selector
'MemberExpression > TSNonNullExpression.object > :not(ChainExpression)',
'CallExpression > TSNonNullExpression.callee > :not(ChainExpression)',
].join(', ')](child) {
// selector guarantees this assertion
const node = child.parent;
let current = child;
while (current) {
switch (current.type) {
case experimental_utils_1.AST_NODE_TYPES.MemberExpression:
if (current.optional) {
// found an optional chain! stop traversing
break;
}
current = current.object;
continue;
case experimental_utils_1.AST_NODE_TYPES.CallExpression:
if (current.optional) {
// found an optional chain! stop traversing
break;
}
current = current.callee;
continue;
default:
// something that's not a ChainElement, which means this is not an optional chain we want to check
return;
}
}
context.report({
node,
messageId: 'noNonNullOptionalChain',
// use a suggestion instead of a fixer, because this can obviously break type checks
suggest: [
{
messageId: 'suggestRemovingNonNull',
fix(fixer) {
return fixer.removeRange([node.range[1] - 1, node.range[1]]);
},
},
],
});
} });
},
});
//# sourceMappingURL=no-non-null-asserted-optional-chain.js.map