Skip to content

Commit

Permalink
no-duplicate-spread-property: handle spread of type variables
Browse files Browse the repository at this point in the history
Fixes: #447
  • Loading branch information
ajafff committed Oct 31, 2018
1 parent 037fe63 commit d8b7b61
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,36 @@ var v: any;
...get<{bar: number} & Record<string, number>>(),
bar: 1,
});

function test<T, U extends T, V extends any>(t: T, u: U, v: V) {
({foo: 1, ...t, ...u, ...v});
}

function test2<T extends Record<'foo', number>, U extends T, V extends T & Record<'bar', number>>(t: T, u: U, v: V) {
({foo: 1, bar: 1, ...t});
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
({foo: 1, bar: 1, ...u});
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
({...t, ...u});
({...t, ...get<T>()});
({...u, ...t});
({...t, ...u, foo: 1});
({foo: 1, bar: 1, ...get<T & {bar: number}>()});
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
~~~ [error no-duplicate-spread-property: Property 'bar' is overridden later.]
({foo: 1, bar: 1, ...v});
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
~~~ [error no-duplicate-spread-property: Property 'bar' is overridden later.]
}

function test3<T>(t: T) {
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {bar: 1}>()});
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {foo: 2}>()});
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
}

function test4<T extends {foo: 1} | {bar: 1}, U extends {foo: 1} | {foo: 2}>(t: T, u: U) {
({foo: 1, bar: 1, ...t});
({foo: 1, bar: 1, ...u});
~~~ [error no-duplicate-spread-property: Property 'foo' is overridden later.]
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,28 @@ var v: any;
...get<{bar: number} & Record<string, number>>(),
bar: 1,
});

function test<T, U extends T, V extends any>(t: T, u: U, v: V) {
({foo: 1, ...t, ...u, ...v});
}

function test2<T extends Record<'foo', number>, U extends T, V extends T & Record<'bar', number>>(t: T, u: U, v: V) {
({foo: 1, bar: 1, ...t});
({foo: 1, bar: 1, ...u});
({...t, ...u});
({...t, ...get<T>()});
({...u, ...t});
({...t, ...u, foo: 1});
({foo: 1, bar: 1, ...get<T & {bar: number}>()});
({foo: 1, bar: 1, ...v});
}

function test3<T>(t: T) {
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {bar: 1}>()});
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {foo: 2}>()});
}

function test4<T extends {foo: 1} | {bar: 1}, U extends {foo: 1} | {foo: 2}>(t: T, u: U) {
({foo: 1, bar: 1, ...t});
({foo: 1, bar: 1, ...u});
}
16 changes: 11 additions & 5 deletions packages/mimir/src/rules/no-duplicate-spread-property.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TypedRule, excludeDeclarationFiles, requiresCompilerOption } from '@fimbul/ymir';
import * as ts from 'typescript';
import { isReassignmentTarget, isObjectType, unionTypeParts, isClassLikeDeclaration, getPropertyName, isIntersectionType } from 'tsutils';
import { isReassignmentTarget, isObjectType, isClassLikeDeclaration, getPropertyName, isIntersectionType, isUnionType } from 'tsutils';
import { lateBoundPropertyNames } from '../utils';

interface PropertyInfo {
Expand Down Expand Up @@ -87,19 +87,25 @@ export class Rule extends TypedRule {
}

private getPropertyInfoFromSpread(node: ts.Expression): PropertyInfo {
const type = this.checker.getTypeAtLocation(node)!;
return unionTypeParts(type).map(getPropertyInfoFromType).reduce(unionPropertyInfo);
return getPropertyInfoFromType(this.checker.getTypeAtLocation(node)!);
}
}

function getPropertyInfoFromType(type: ts.Type): PropertyInfo {
if (isUnionType(type))
return type.types.map(getPropertyInfoFromType).reduce(unionPropertyInfo);
if (isIntersectionType(type))
return type.types.map(getPropertyInfoFromType).reduce(intersectPropertyInfo);
if (type.flags & ts.TypeFlags.Instantiable) {
const constraint = type.getConstraint();
if (constraint === undefined)
return emptyPropertyInfo;
return {...getPropertyInfoFromType(constraint), known: false};
}
if (!isObjectType(type))
return emptyPropertyInfo;
const result: PropertyInfo = {
known: (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 ||
type.getStringIndexType() === undefined && type.getNumberIndexType() === undefined,
known: type.getStringIndexType() === undefined && type.getNumberIndexType() === undefined,
names: [],
assignedNames: [],
};
Expand Down
25 changes: 25 additions & 0 deletions packages/mimir/test/no-duplicate-spread-property/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,28 @@ var v: any;
...get<{bar: number} & Record<string, number>>(),
bar: 1,
});

function test<T, U extends T, V extends any>(t: T, u: U, v: V) {
({foo: 1, ...t, ...u, ...v});
}

function test2<T extends Record<'foo', number>, U extends T, V extends T & Record<'bar', number>>(t: T, u: U, v: V) {
({foo: 1, bar: 1, ...t});
({foo: 1, bar: 1, ...u});
({...t, ...u});
({...t, ...get<T>()});
({...u, ...t});
({...t, ...u, foo: 1});
({foo: 1, bar: 1, ...get<T & {bar: number}>()});
({foo: 1, bar: 1, ...v});
}

function test3<T>(t: T) {
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {bar: 1}>()});
({foo: 1, bar: 1, ...get<T extends number ? {foo: 1} : {foo: 2}>()});
}

function test4<T extends {foo: 1} | {bar: 1}, U extends {foo: 1} | {foo: 2}>(t: T, u: U) {
({foo: 1, bar: 1, ...t});
({foo: 1, bar: 1, ...u});
}

0 comments on commit d8b7b61

Please sign in to comment.