UNPKG

18.1 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 util = __importStar(require("../util"));
24exports.default = util.createRule({
25 name: 'unified-signatures',
26 meta: {
27 docs: {
28 description: 'Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter',
29 // too opinionated to be recommended
30 recommended: false,
31 },
32 type: 'suggestion',
33 messages: {
34 omittingRestParameter: '{{failureStringStart}} with a rest parameter.',
35 omittingSingleParameter: '{{failureStringStart}} with an optional parameter.',
36 singleParameterDifference: '{{failureStringStart}} taking `{{type1}} | {{type2}}`.',
37 },
38 schema: [],
39 },
40 defaultOptions: [],
41 create(context) {
42 const sourceCode = context.getSourceCode();
43 //----------------------------------------------------------------------
44 // Helpers
45 //----------------------------------------------------------------------
46 function failureStringStart(otherLine) {
47 // For only 2 overloads we don't need to specify which is the other one.
48 const overloads = otherLine === undefined
49 ? 'These overloads'
50 : `This overload and the one on line ${otherLine}`;
51 return `${overloads} can be combined into one signature`;
52 }
53 function addFailures(failures) {
54 for (const failure of failures) {
55 const { unify, only2 } = failure;
56 switch (unify.kind) {
57 case 'single-parameter-difference': {
58 const { p0, p1 } = unify;
59 const lineOfOtherOverload = only2 ? undefined : p0.loc.start.line;
60 const typeAnnotation0 = isTSParameterProperty(p0)
61 ? p0.parameter.typeAnnotation
62 : p0.typeAnnotation;
63 const typeAnnotation1 = isTSParameterProperty(p1)
64 ? p1.parameter.typeAnnotation
65 : p1.typeAnnotation;
66 context.report({
67 loc: p1.loc,
68 messageId: 'singleParameterDifference',
69 data: {
70 failureStringStart: failureStringStart(lineOfOtherOverload),
71 type1: sourceCode.getText(typeAnnotation0 === null || typeAnnotation0 === void 0 ? void 0 : typeAnnotation0.typeAnnotation),
72 type2: sourceCode.getText(typeAnnotation1 === null || typeAnnotation1 === void 0 ? void 0 : typeAnnotation1.typeAnnotation),
73 },
74 node: p1,
75 });
76 break;
77 }
78 case 'extra-parameter': {
79 const { extraParameter, otherSignature } = unify;
80 const lineOfOtherOverload = only2
81 ? undefined
82 : otherSignature.loc.start.line;
83 context.report({
84 loc: extraParameter.loc,
85 messageId: extraParameter.type === utils_1.AST_NODE_TYPES.RestElement
86 ? 'omittingRestParameter'
87 : 'omittingSingleParameter',
88 data: {
89 failureStringStart: failureStringStart(lineOfOtherOverload),
90 },
91 node: extraParameter,
92 });
93 }
94 }
95 }
96 }
97 function checkOverloads(signatures, typeParameters) {
98 const result = [];
99 const isTypeParameter = getIsTypeParameter(typeParameters);
100 for (const overloads of signatures) {
101 if (overloads.length === 2) {
102 const signature0 = overloads[0].value || overloads[0];
103 const signature1 = overloads[1].value || overloads[1];
104 const unify = compareSignatures(signature0, signature1, isTypeParameter);
105 if (unify !== undefined) {
106 result.push({ unify, only2: true });
107 }
108 }
109 else {
110 forEachPair(overloads, (a, b) => {
111 const signature0 = a.value || a;
112 const signature1 = b.value || b;
113 const unify = compareSignatures(signature0, signature1, isTypeParameter);
114 if (unify !== undefined) {
115 result.push({ unify, only2: false });
116 }
117 });
118 }
119 }
120 return result;
121 }
122 function compareSignatures(a, b, isTypeParameter) {
123 if (!signaturesCanBeUnified(a, b, isTypeParameter)) {
124 return undefined;
125 }
126 return a.params.length === b.params.length
127 ? signaturesDifferBySingleParameter(a.params, b.params)
128 : signaturesDifferByOptionalOrRestParameter(a, b);
129 }
130 function signaturesCanBeUnified(a, b, isTypeParameter) {
131 // Must return the same type.
132 const aTypeParams = a.typeParameters !== undefined ? a.typeParameters.params : undefined;
133 const bTypeParams = b.typeParameters !== undefined ? b.typeParameters.params : undefined;
134 return (typesAreEqual(a.returnType, b.returnType) &&
135 // Must take the same type parameters.
136 // If one uses a type parameter (from outside) and the other doesn't, they shouldn't be joined.
137 util.arraysAreEqual(aTypeParams, bTypeParams, typeParametersAreEqual) &&
138 signatureUsesTypeParameter(a, isTypeParameter) ===
139 signatureUsesTypeParameter(b, isTypeParameter));
140 }
141 /** Detect `a(x: number, y: number, z: number)` and `a(x: number, y: string, z: number)`. */
142 function signaturesDifferBySingleParameter(types1, types2) {
143 const index = getIndexOfFirstDifference(types1, types2, parametersAreEqual);
144 if (index === undefined) {
145 return undefined;
146 }
147 // If remaining arrays are equal, the signatures differ by just one parameter type
148 if (!util.arraysAreEqual(types1.slice(index + 1), types2.slice(index + 1), parametersAreEqual)) {
149 return undefined;
150 }
151 const a = types1[index];
152 const b = types2[index];
153 // Can unify `a?: string` and `b?: number`. Can't unify `...args: string[]` and `...args: number[]`.
154 // See https://github.com/Microsoft/TypeScript/issues/5077
155 return parametersHaveEqualSigils(a, b) &&
156 a.type !== utils_1.AST_NODE_TYPES.RestElement
157 ? { kind: 'single-parameter-difference', p0: a, p1: b }
158 : undefined;
159 }
160 /**
161 * Detect `a(): void` and `a(x: number): void`.
162 * Returns the parameter declaration (`x: number` in this example) that should be optional/rest, and overload it's a part of.
163 */
164 function signaturesDifferByOptionalOrRestParameter(a, b) {
165 const sig1 = a.params;
166 const sig2 = b.params;
167 const minLength = Math.min(sig1.length, sig2.length);
168 const longer = sig1.length < sig2.length ? sig2 : sig1;
169 const shorter = sig1.length < sig2.length ? sig1 : sig2;
170 const shorterSig = sig1.length < sig2.length ? a : b;
171 // If one is has 2+ parameters more than the other, they must all be optional/rest.
172 // Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z)
173 // Not allowed: f() and f(x, y)
174 for (let i = minLength + 1; i < longer.length; i++) {
175 if (!parameterMayBeMissing(longer[i])) {
176 return undefined;
177 }
178 }
179 for (let i = 0; i < minLength; i++) {
180 const sig1i = sig1[i];
181 const sig2i = sig2[i];
182 const typeAnnotation1 = isTSParameterProperty(sig1i)
183 ? sig1i.parameter.typeAnnotation
184 : sig1i.typeAnnotation;
185 const typeAnnotation2 = isTSParameterProperty(sig2i)
186 ? sig2i.parameter.typeAnnotation
187 : sig2i.typeAnnotation;
188 if (!typesAreEqual(typeAnnotation1, typeAnnotation2)) {
189 return undefined;
190 }
191 }
192 if (minLength > 0 &&
193 shorter[minLength - 1].type === utils_1.AST_NODE_TYPES.RestElement) {
194 return undefined;
195 }
196 return {
197 extraParameter: longer[longer.length - 1],
198 kind: 'extra-parameter',
199 otherSignature: shorterSig,
200 };
201 }
202 /** Given type parameters, returns a function to test whether a type is one of those parameters. */
203 function getIsTypeParameter(typeParameters) {
204 if (typeParameters === undefined) {
205 return (() => false);
206 }
207 const set = new Set();
208 for (const t of typeParameters.params) {
209 set.add(t.name.name);
210 }
211 return (typeName => set.has(typeName));
212 }
213 /** True if any of the outer type parameters are used in a signature. */
214 function signatureUsesTypeParameter(sig, isTypeParameter) {
215 return sig.params.some((p) => typeContainsTypeParameter(isTSParameterProperty(p)
216 ? p.parameter.typeAnnotation
217 : p.typeAnnotation));
218 function typeContainsTypeParameter(type) {
219 if (!type) {
220 return false;
221 }
222 if (type.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
223 const typeName = type.typeName;
224 if (isIdentifier(typeName) && isTypeParameter(typeName.name)) {
225 return true;
226 }
227 }
228 return typeContainsTypeParameter(type.typeAnnotation ||
229 type.elementType);
230 }
231 }
232 function isTSParameterProperty(node) {
233 return (node.type ===
234 utils_1.AST_NODE_TYPES.TSParameterProperty);
235 }
236 function parametersAreEqual(a, b) {
237 const typeAnnotationA = isTSParameterProperty(a)
238 ? a.parameter.typeAnnotation
239 : a.typeAnnotation;
240 const typeAnnotationB = isTSParameterProperty(b)
241 ? b.parameter.typeAnnotation
242 : b.typeAnnotation;
243 return (parametersHaveEqualSigils(a, b) &&
244 typesAreEqual(typeAnnotationA, typeAnnotationB));
245 }
246 /** True for optional/rest parameters. */
247 function parameterMayBeMissing(p) {
248 const optional = isTSParameterProperty(p)
249 ? p.parameter.optional
250 : p.optional;
251 return p.type === utils_1.AST_NODE_TYPES.RestElement || optional;
252 }
253 /** False if one is optional and the other isn't, or one is a rest parameter and the other isn't. */
254 function parametersHaveEqualSigils(a, b) {
255 const optionalA = isTSParameterProperty(a)
256 ? a.parameter.optional
257 : a.optional;
258 const optionalB = isTSParameterProperty(b)
259 ? b.parameter.optional
260 : b.optional;
261 return ((a.type === utils_1.AST_NODE_TYPES.RestElement) ===
262 (b.type === utils_1.AST_NODE_TYPES.RestElement) &&
263 (optionalA !== undefined) === (optionalB !== undefined));
264 }
265 function typeParametersAreEqual(a, b) {
266 return (a.name.name === b.name.name &&
267 constraintsAreEqual(a.constraint, b.constraint));
268 }
269 function typesAreEqual(a, b) {
270 return (a === b ||
271 (a !== undefined &&
272 b !== undefined &&
273 sourceCode.getText(a.typeAnnotation) ===
274 sourceCode.getText(b.typeAnnotation)));
275 }
276 function constraintsAreEqual(a, b) {
277 return (a === b || (a !== undefined && b !== undefined && a.type === b.type));
278 }
279 /* Returns the first index where `a` and `b` differ. */
280 function getIndexOfFirstDifference(a, b, equal) {
281 for (let i = 0; i < a.length && i < b.length; i++) {
282 if (!equal(a[i], b[i])) {
283 return i;
284 }
285 }
286 return undefined;
287 }
288 /** Calls `action` for every pair of values in `values`. */
289 function forEachPair(values, action) {
290 for (let i = 0; i < values.length; i++) {
291 for (let j = i + 1; j < values.length; j++) {
292 action(values[i], values[j]);
293 }
294 }
295 }
296 const scopes = [];
297 let currentScope = {
298 overloads: new Map(),
299 };
300 function createScope(parent, typeParameters) {
301 currentScope && scopes.push(currentScope);
302 currentScope = {
303 overloads: new Map(),
304 parent,
305 typeParameters,
306 };
307 }
308 function checkScope() {
309 const failures = checkOverloads(Array.from(currentScope.overloads.values()), currentScope.typeParameters);
310 addFailures(failures);
311 currentScope = scopes.pop();
312 }
313 function addOverload(signature, key, containingNode) {
314 key = key !== null && key !== void 0 ? key : getOverloadKey(signature);
315 if (currentScope &&
316 (containingNode || signature).parent === currentScope.parent) {
317 const overloads = currentScope.overloads.get(key);
318 if (overloads !== undefined) {
319 overloads.push(signature);
320 }
321 else {
322 currentScope.overloads.set(key, [signature]);
323 }
324 }
325 }
326 //----------------------------------------------------------------------
327 // Public
328 //----------------------------------------------------------------------
329 return {
330 Program: createScope,
331 TSModuleBlock: createScope,
332 TSInterfaceDeclaration(node) {
333 createScope(node.body, node.typeParameters);
334 },
335 ClassDeclaration(node) {
336 createScope(node.body, node.typeParameters);
337 },
338 TSTypeLiteral: createScope,
339 // collect overloads
340 TSDeclareFunction(node) {
341 var _a, _b;
342 const exportingNode = getExportingNode(node);
343 addOverload(node, (_b = (_a = node.id) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : exportingNode === null || exportingNode === void 0 ? void 0 : exportingNode.type, exportingNode);
344 },
345 TSCallSignatureDeclaration: addOverload,
346 TSConstructSignatureDeclaration: addOverload,
347 TSMethodSignature: addOverload,
348 TSAbstractMethodDefinition(node) {
349 if (!node.value.body) {
350 addOverload(node);
351 }
352 },
353 MethodDefinition(node) {
354 if (!node.value.body) {
355 addOverload(node);
356 }
357 },
358 // validate scopes
359 'Program:exit': checkScope,
360 'TSModuleBlock:exit': checkScope,
361 'TSInterfaceDeclaration:exit': checkScope,
362 'ClassDeclaration:exit': checkScope,
363 'TSTypeLiteral:exit': checkScope,
364 };
365 },
366});
367function getExportingNode(node) {
368 return node.parent &&
369 (node.parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
370 node.parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration)
371 ? node.parent
372 : undefined;
373}
374function getOverloadKey(node) {
375 const info = getOverloadInfo(node);
376 return ((node.computed ? '0' : '1') +
377 (node.static ? '0' : '1') +
378 info);
379}
380function getOverloadInfo(node) {
381 switch (node.type) {
382 case utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration:
383 return 'constructor';
384 case utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration:
385 return '()';
386 default: {
387 const { key } = node;
388 return isIdentifier(key) ? key.name : key.raw;
389 }
390 }
391}
392function isIdentifier(node) {
393 return node.type === utils_1.AST_NODE_TYPES.Identifier;
394}
395//# sourceMappingURL=unified-signatures.js.map
\No newline at end of file