1 | "use strict";
|
2 | var __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 | }));
|
9 | var __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 | });
|
14 | var __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 | };
|
21 | Object.defineProperty(exports, "__esModule", { value: true });
|
22 | const utils_1 = require("@typescript-eslint/utils");
|
23 | const util = __importStar(require("../util"));
|
24 | exports.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 |
|
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 |
|
45 |
|
46 | function failureStringStart(otherLine) {
|
47 |
|
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 |
|
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 |
|
136 |
|
137 | util.arraysAreEqual(aTypeParams, bTypeParams, typeParametersAreEqual) &&
|
138 | signatureUsesTypeParameter(a, isTypeParameter) ===
|
139 | signatureUsesTypeParameter(b, isTypeParameter));
|
140 | }
|
141 |
|
142 | function signaturesDifferBySingleParameter(types1, types2) {
|
143 | const index = getIndexOfFirstDifference(types1, types2, parametersAreEqual);
|
144 | if (index === undefined) {
|
145 | return undefined;
|
146 | }
|
147 |
|
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 |
|
154 |
|
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 |
|
162 |
|
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 |
|
172 |
|
173 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
359 | 'Program:exit': checkScope,
|
360 | 'TSModuleBlock:exit': checkScope,
|
361 | 'TSInterfaceDeclaration:exit': checkScope,
|
362 | 'ClassDeclaration:exit': checkScope,
|
363 | 'TSTypeLiteral:exit': checkScope,
|
364 | };
|
365 | },
|
366 | });
|
367 | function 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 | }
|
374 | function getOverloadKey(node) {
|
375 | const info = getOverloadInfo(node);
|
376 | return ((node.computed ? '0' : '1') +
|
377 | (node.static ? '0' : '1') +
|
378 | info);
|
379 | }
|
380 | function 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 | }
|
392 | function isIdentifier(node) {
|
393 | return node.type === utils_1.AST_NODE_TYPES.Identifier;
|
394 | }
|
395 |
|
\ | No newline at end of file |