Skip to content

Commit

Permalink
fix(api): support intersections of mapped types
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsdev committed Oct 27, 2022
1 parent d507076 commit 799f81c
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 8 deletions.
15 changes: 13 additions & 2 deletions packages/api/src/localizedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,16 @@ function getChildren(
}

// TODO: intersection properties
case "intersection":
case "intersection": {
const { types, properties, indexInfos = [] } = info

return [
...indexInfos.map((info) => getLocalizedIndex(info)),
...properties.map(localize),
...types.map(localize),
]
}

case "union": {
const { types } = info
return types.map(localize)
Expand Down Expand Up @@ -625,10 +634,12 @@ function getChildren(
}

function getLocalizedIndex(indexInfo: IndexInfo) {
const indexSymbol = wrapSafe(localizeSymbol)(indexInfo.parameterSymbol)

return createChild({
kindText: "index",
kind: "index_info",
symbol: wrapSafe(localizeSymbol)(indexInfo.parameterSymbol),
symbol: indexSymbol,
children: [
...(indexInfo.parameterType
? [
Expand Down
17 changes: 14 additions & 3 deletions packages/api/src/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
getIntersectionTypesFlat,
getSymbolType,
getTypeId,
isPureObject,
wrapSafe,
isArrayType,
getTypeArguments,
Expand All @@ -42,6 +41,7 @@ import {
removeDuplicates,
narrowDeclarationForLocation,
TSIndexInfo,
isPureObjectOrMappedTypeShallow,
} from "./util"

const maxDepthExceeded: TypeInfo = { kind: "max_depth", id: getEmptyTypeId() }
Expand Down Expand Up @@ -411,19 +411,25 @@ function _generateTypeTree(
} else if (flags & ts.TypeFlags.Intersection) {
const allTypes = getIntersectionTypesFlat(tsCtx, type)
const types = parseTypes(
allTypes.filter((t) => !isPureObject(tsCtx, t))
allTypes.filter(
(t) => !isPureObjectOrMappedTypeShallow(tsCtx, t)
)
)

const properties = parseSymbols(type.getProperties())
const indexInfos = getIndexInfos(tsCtx, type).map(getIndexInfo)

if (types.length === 0) {
return {
kind: "object",
properties,
...(isNonEmpty(indexInfos) && { indexInfos }),
}
} else {
return {
kind: "intersection",
types,
...(isNonEmpty(indexInfos) && { indexInfos }),
properties,
}
}
Expand Down Expand Up @@ -735,7 +741,11 @@ export function getTypeInfoChildren(info: TypeInfo): TypeInfo[] {
}

case "intersection": {
return [...info.types, ...info.properties]
return [
...info.types,
...info.properties,
...(info.indexInfos?.flatMap(mapIndexInfo) ?? []),
]
}

case "union": {
Expand Down Expand Up @@ -823,6 +833,7 @@ export function getTypeInfoSymbols(info: TypeInfo): SymbolInfo[] {

function _getTypeInfoSymbols(info: TypeInfo): (SymbolInfo | undefined)[] {
switch (info.kind) {
case "intersection":
case "object": {
return [...(info.indexInfos?.map(mapIndexInfo) ?? [])]
}
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export type TypeInfoNoId = {
kind: "intersection"
properties: TypeInfo[]
types: TypeInfo[]
indexInfos?: IndexInfo[]
}
| {
kind: "index"
Expand Down
18 changes: 15 additions & 3 deletions packages/api/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,17 +416,29 @@ export function isPureObject(

return (
(!!(type.flags & ts.TypeFlags.Object) &&
getSignaturesOfType(ctx, type).length === 0 &&
getIndexInfos(ctx, type).length === 0 &&
!isArrayType(ctx, type) &&
!isTupleType(ctx, type)) ||
isPureObjectOrMappedTypeShallow(ctx, type)) ||
(!!(type.flags & ts.TypeFlags.Intersection) &&
(type as ts.IntersectionType).types.every((t) =>
isPureObject(ctx, t)
))
)
}

export function isPureObjectOrMappedTypeShallow(
ctx: TypescriptContext,
type: ts.Type
): type is ts.ObjectType {
const { ts } = ctx

return (
!!(type.flags & ts.TypeFlags.Object) &&
getSignaturesOfType(ctx, type).length === 0 &&
!isArrayType(ctx, type) &&
!isTupleType(ctx, type)
)
}

export function getIntersectionTypesFlat(
{ ts }: TypescriptContext,
...types: ts.Type[]
Expand Down
210 changes: 210 additions & 0 deletions tests/baselines/reference/intersectionWithIndex.localized.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
=== intersectionWithIndex.ts ===

type intersectionWithIndex = { [index: string]: string } & { a: "asd" };
> intersectionWithIndex --- {
"kindText": "object",
"kind": "object",
"symbol": {
"name": "intersectionWithIndex",
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 5
},
"end": {
"line": 0,
"character": 26
}
}
}
]
},
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 5
},
"end": {
"line": 0,
"character": 26
}
}
}
],
"children": [
{
"kindText": "index",
"kind": "index_info",
"symbol": {
"name": "index",
"isArgument": true,
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 32
},
"end": {
"line": 0,
"character": 37
}
}
}
]
},
"children": [
{
"kindText": "string",
"kind": "primitive",
"primitiveKind": "string",
"purpose": "index_type",
"children": [],
"_id": "1"
},
{
"reference": "1"
}
]
},
{
"kindText": "\"asd\"",
"kind": "string_literal",
"symbol": {
"name": "a",
"property": true,
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 61
},
"end": {
"line": 0,
"character": 62
}
}
}
]
},
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 61
},
"end": {
"line": 0,
"character": 62
}
}
}
],
"children": [],
"_id": "2"
}
],
"_id": "0"
}
> { [index: string]: string } & { a: "asd" }
> { [index: string]: string } & { a: "asd" }
> { [index: string]: string }
> [index: string]: string
> [index: string]: string
> index: string
> index: string
> index --- {
"kindText": "string",
"kind": "primitive",
"primitiveKind": "string",
"symbol": {
"name": "index",
"isArgument": true,
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 32
},
"end": {
"line": 0,
"character": 37
}
}
}
]
},
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 32
},
"end": {
"line": 0,
"character": 37
}
}
}
],
"children": [],
"_id": "0"
}
> { a: "asd" }
> a: "asd"
> a: "asd"
> a --- {
"kindText": "\"asd\"",
"kind": "string_literal",
"symbol": {
"name": "a",
"property": true,
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 61
},
"end": {
"line": 0,
"character": 62
}
}
}
]
},
"locations": [
{
"fileName": "cases/intersectionWithIndex.ts",
"range": {
"start": {
"line": 0,
"character": 61
},
"end": {
"line": 0,
"character": 62
}
}
}
],
"children": [],
"_id": "0"
}
Loading

0 comments on commit 799f81c

Please sign in to comment.