UNPKG

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