Skip to content

Commit d8b7b61

Browse files
committed
no-duplicate-spread-property: handle spread of type variables
Fixes: #447
1 parent 037fe63 commit d8b7b61

File tree

4 files changed

+94
-5
lines changed

4 files changed

+94
-5
lines changed

baselines/packages/mimir/test/no-duplicate-spread-property/default/test.ts.lint

+33
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,36 @@ var v: any;
295295
...get<{bar: number} & Record<string, number>>(),
296296
bar: 1,
297297
});
298+
299+
function test<T, U extends T, V extends any>(t: T, u: U, v: V) {
300+
({foo: 1, ...t, ...u, ...v});
301+
}
302+
303+
function test2<T extends Record<'foo', number>, U extends T, V extends T & Record<'bar', number>>(t: T, u: U, v: V) {
304+
({foo: 1, bar: 1, ...t});
305+
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
306+
({foo: 1, bar: 1, ...u});
307+
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
308+
({...t, ...u});
309+
({...t, ...get<T>()});
310+
({...u, ...t});
311+
({...t, ...u, foo: 1});
312+
({foo: 1, bar: 1, ...get<T & {bar: number}>()});
313+
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
314+
~~~ [error no-duplicate-spread-property: Property 'bar' is overridden later.]
315+
({foo: 1, bar: 1, ...v});
316+
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
317+
~~~ [error no-duplicate-spread-property: Property 'bar' is overridden later.]
318+
}
319+
320+
function test3<T>(t: T) {
321+
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {bar: 1}>()});
322+
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {foo: 2}>()});
323+
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
324+
}
325+
326+
function test4<T extends {foo: 1} | {bar: 1}, U extends {foo: 1} | {foo: 2}>(t: T, u: U) {
327+
({foo: 1, bar: 1, ...t});
328+
({foo: 1, bar: 1, ...u});
329+
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
330+
}

baselines/packages/mimir/test/no-duplicate-spread-property/loose/test.ts.lint

+25
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,28 @@ var v: any;
263263
...get<{bar: number} & Record<string, number>>(),
264264
bar: 1,
265265
});
266+
267+
function test<T, U extends T, V extends any>(t: T, u: U, v: V) {
268+
({foo: 1, ...t, ...u, ...v});
269+
}
270+
271+
function test2<T extends Record<'foo', number>, U extends T, V extends T & Record<'bar', number>>(t: T, u: U, v: V) {
272+
({foo: 1, bar: 1, ...t});
273+
({foo: 1, bar: 1, ...u});
274+
({...t, ...u});
275+
({...t, ...get<T>()});
276+
({...u, ...t});
277+
({...t, ...u, foo: 1});
278+
({foo: 1, bar: 1, ...get<T & {bar: number}>()});
279+
({foo: 1, bar: 1, ...v});
280+
}
281+
282+
function test3<T>(t: T) {
283+
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {bar: 1}>()});
284+
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {foo: 2}>()});
285+
}
286+
287+
function test4<T extends {foo: 1} | {bar: 1}, U extends {foo: 1} | {foo: 2}>(t: T, u: U) {
288+
({foo: 1, bar: 1, ...t});
289+
({foo: 1, bar: 1, ...u});
290+
}

packages/mimir/src/rules/no-duplicate-spread-property.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TypedRule, excludeDeclarationFiles, requiresCompilerOption } from '@fimbul/ymir';
22
import * as ts from 'typescript';
3-
import { isReassignmentTarget, isObjectType, unionTypeParts, isClassLikeDeclaration, getPropertyName, isIntersectionType } from 'tsutils';
3+
import { isReassignmentTarget, isObjectType, isClassLikeDeclaration, getPropertyName, isIntersectionType, isUnionType } from 'tsutils';
44
import { lateBoundPropertyNames } from '../utils';
55

66
interface PropertyInfo {
@@ -87,19 +87,25 @@ export class Rule extends TypedRule {
8787
}
8888

8989
private getPropertyInfoFromSpread(node: ts.Expression): PropertyInfo {
90-
const type = this.checker.getTypeAtLocation(node)!;
91-
return unionTypeParts(type).map(getPropertyInfoFromType).reduce(unionPropertyInfo);
90+
return getPropertyInfoFromType(this.checker.getTypeAtLocation(node)!);
9291
}
9392
}
9493

9594
function getPropertyInfoFromType(type: ts.Type): PropertyInfo {
95+
if (isUnionType(type))
96+
return type.types.map(getPropertyInfoFromType).reduce(unionPropertyInfo);
9697
if (isIntersectionType(type))
9798
return type.types.map(getPropertyInfoFromType).reduce(intersectPropertyInfo);
99+
if (type.flags & ts.TypeFlags.Instantiable) {
100+
const constraint = type.getConstraint();
101+
if (constraint === undefined)
102+
return emptyPropertyInfo;
103+
return {...getPropertyInfoFromType(constraint), known: false};
104+
}
98105
if (!isObjectType(type))
99106
return emptyPropertyInfo;
100107
const result: PropertyInfo = {
101-
known: (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 ||
102-
type.getStringIndexType() === undefined && type.getNumberIndexType() === undefined,
108+
known: type.getStringIndexType() === undefined && type.getNumberIndexType() === undefined,
103109
names: [],
104110
assignedNames: [],
105111
};

packages/mimir/test/no-duplicate-spread-property/test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,28 @@ var v: any;
263263
...get<{bar: number} & Record<string, number>>(),
264264
bar: 1,
265265
});
266+
267+
function test<T, U extends T, V extends any>(t: T, u: U, v: V) {
268+
({foo: 1, ...t, ...u, ...v});
269+
}
270+
271+
function test2<T extends Record<'foo', number>, U extends T, V extends T & Record<'bar', number>>(t: T, u: U, v: V) {
272+
({foo: 1, bar: 1, ...t});
273+
({foo: 1, bar: 1, ...u});
274+
({...t, ...u});
275+
({...t, ...get<T>()});
276+
({...u, ...t});
277+
({...t, ...u, foo: 1});
278+
({foo: 1, bar: 1, ...get<T & {bar: number}>()});
279+
({foo: 1, bar: 1, ...v});
280+
}
281+
282+
function test3<T>(t: T) {
283+
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {bar: 1}>()});
284+
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {foo: 2}>()});
285+
}
286+
287+
function test4<T extends {foo: 1} | {bar: 1}, U extends {foo: 1} | {foo: 2}>(t: T, u: U) {
288+
({foo: 1, bar: 1, ...t});
289+
({foo: 1, bar: 1, ...u});
290+
}

0 commit comments

Comments
 (0)