Skip to content

Commit

Permalink
fix: correct intersection with apparent type (#61)
Browse files Browse the repository at this point in the history
* test

* yarn and type name

* remove intersection

* version bump

---------

Co-authored-by: Myles Murphy <mylesmurphy@Myless-MacBook-Pro.local>
  • Loading branch information
mylesmmurphy and Myles Murphy authored Feb 16, 2025
1 parent 47411c8 commit a587ff1
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 73 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "prettify-ts-monorepo",
"publisher": "MylesMurphy",
"license": "MIT",
"version": "0.1.6",
"version": "0.2.0",
"private": true,
"workspaces": {
"packages": ["packages/*"]
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@prettify-ts/typescript-plugin",
"version": "0.1.6",
"version": "0.2.0",
"main": "./out",
"license": "MIT",
"private": "true",
Expand Down
97 changes: 37 additions & 60 deletions packages/typescript-plugin/src/type-tree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,27 +150,40 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type
const typeName = checker.typeToString(type, undefined, typescript.TypeFormatFlags.NoTruncation)
const apparentType = checker.getApparentType(type)

if (isPrimitiveType(type)) {
if (isPrimitiveType(type) || isPrimitiveType(apparentType)) {
return {
kind: 'primitive',
typeName
}
}

// Prevent infinite recursion when encountering circular references
// Guarantueed to be a reference if the type has been visited before
if (visited.has(type) || options.skippedTypeNames.includes(typeName)) {
if (options.skippedTypeNames.includes(typeName)) {
return {
kind: 'reference',
typeName
}
}

// Prevent infinite recursion when encountering circular references
if (visited.has(type)) {
if (typeName.includes('{') || typeName.includes('[') || typeName.includes('(')) {
return {
kind: 'reference',
typeName: '...'
}
}

return {
kind: 'reference',
typeName: checker.typeToString(apparentType, undefined, typescript.TypeFormatFlags.NoTruncation)
}
}

visited.add(type)

if (type.isUnion()) {
const excessMembers = Math.max(0, type.types.length - options.maxUnionMembers)
const types = type.types
if (apparentType.isUnion()) {
const excessMembers = Math.max(0, apparentType.types.length - options.maxUnionMembers)
const types = apparentType.types
.slice(0, options.maxUnionMembers)
.sort(sortUnionTypes)
.map(t => getTypeTree(t, depth, new Set(visited)))
Expand All @@ -183,51 +196,11 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type
}
}

if (type?.symbol?.flags & typescript.SymbolFlags.EnumMember && type.symbol.parent) {
if (apparentType?.symbol?.flags & typescript.SymbolFlags.EnumMember && apparentType.symbol.parent) {
return {
kind: 'enum',
typeName,
member: `${type.symbol.parent.name}.${type.symbol.name}`
}
}

if (type.isIntersection()) {
const intersectionTypes = type.types.map(t => getTypeTree(t, depth, new Set(visited)))

// Combine intersection objects into a single object, if possible
// Example: { a: string } & { b: number } => { a: string, b: number }
// If the intersection contains a primitive type, return as a reference
// Example: { a: string } & number => A & number
const types: TypeTree[] = intersectionTypes.filter(t => t.kind !== 'object')

const objectTypes = intersectionTypes
.filter((t): t is Extract<TypeTree, { kind: 'object' }> => t.kind === 'object')

if (objectTypes.length) {
const depthMaxProps = depth >= 1 ? options.maxSubProperties : options.maxProperties

// Combine all properties from object types
let properties = objectTypes.flatMap(t => t.properties)

// Calculate excess properties to hide
let excessProperties = objectTypes.reduce((acc, t) => acc + t.excessProperties, 0)
excessProperties += Math.max(0, properties.length - depthMaxProps)

// Limit properties to the maximum allowed
properties = properties.slice(0, depthMaxProps)

types.push({
kind: 'object',
typeName,
properties,
excessProperties
})
}

return {
kind: 'intersection',
typeName,
types
member: `${apparentType.symbol.parent.name}.${apparentType.symbol.name}`
}
}

Expand Down Expand Up @@ -280,11 +253,11 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type
}
}

if (checker.isTupleType(type)) {
if (checker.isTupleType(apparentType)) {
const elementTypes = checker
.getTypeArguments(type as ts.TupleTypeReference)
.getTypeArguments(apparentType as ts.TupleTypeReference)
.map(t => getTypeTree(t, depth, new Set(visited)))
const readonly = (type as ts.TupleTypeReference)?.target?.readonly ?? false
const readonly = (apparentType as ts.TupleTypeReference)?.target?.readonly ?? false

return {
kind: 'tuple',
Expand All @@ -294,25 +267,29 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type
}
}

if (checker.isArrayType(type)) {
if (checker.isArrayType(apparentType)) {
if (!options.unwrapArrays) {
depth = options.maxDepth
}

const arrayType = checker.getTypeArguments(type as ts.TypeReference)[0]
const arrayType = checker.getTypeArguments(apparentType as ts.TypeReference)[0]
const elementType: TypeTree = arrayType
? getTypeTree(arrayType, depth, new Set(visited))
: { kind: 'primitive', typeName: 'any' }

return {
kind: 'array',
typeName,
readonly: type.getSymbol()?.getName() === 'ReadonlyArray',
readonly: apparentType.getSymbol()?.getName() === 'ReadonlyArray',
elementType
}
}

if (apparentType.isClassOrInterface() || (apparentType.flags & typescript.TypeFlags.Object)) {
if (
apparentType.isClassOrInterface() ||
(apparentType.flags & typescript.TypeFlags.Object) ||
apparentType.getProperties().length > 0
) {
// Resolve how many properties to show based on the maxProperties option
const depthMaxProps = depth >= 1 ? options.maxSubProperties : options.maxProperties

Expand All @@ -321,8 +298,8 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type
typeProperties = typeProperties.filter((symbol) => isPublicProperty(symbol))
}

const stringIndexType = type.getStringIndexType()
const numberIndexType = type.getNumberIndexType()
const stringIndexType = apparentType.getStringIndexType()
const numberIndexType = apparentType.getNumberIndexType()

if (depth >= options.maxDepth) {
// If we've reached the max depth and has a type alias, return it as a reference type
Expand Down Expand Up @@ -362,7 +339,7 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type
if (stringIndexType) {
// If under max properties allowance, add the string index type as a property
if (excessProperties < 0) {
const stringIndexIdentifierName = getIndexIdentifierName(type, 'string')
const stringIndexIdentifierName = getIndexIdentifierName(apparentType, 'string')
properties.push({
name: `[${stringIndexIdentifierName}: string]`,
optional: false,
Expand All @@ -376,7 +353,7 @@ function getTypeTree (type: ts.Type, depth: number, visited: Set<ts.Type>): Type

if (numberIndexType) {
if (excessProperties < 0) {
const numberIndexIdentifierName = getIndexIdentifierName(type, 'number')
const numberIndexIdentifierName = getIndexIdentifierName(apparentType, 'number')
properties.push({
name: `[${numberIndexIdentifierName}: number]`,
optional: false,
Expand Down
1 change: 0 additions & 1 deletion packages/typescript-plugin/src/type-tree/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type TypeFunctionSignature = { returnType: TypeTree, parameters: TypeFunc
*/
export type TypeTree = { typeName: string } & (
| { kind: 'union', excessMembers: number, types: TypeTree[] }
| { kind: 'intersection', types: TypeTree[] }
| { kind: 'object', excessProperties: number, properties: TypeProperty[] }
| { kind: 'tuple', readonly: boolean, elementTypes: TypeTree[] }
| { kind: 'array', readonly: boolean, elementType: TypeTree }
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "MylesMurphy",
"license": "MIT",
"private": true,
"version": "0.1.6",
"version": "0.2.0",
"main": "./out/extension.js",
"workspaces": {
"nohoist": ["**"]
Expand Down
4 changes: 0 additions & 4 deletions packages/vscode-extension/src/stringify-type-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ export function stringifyTypeTree (typeTree: TypeTree, anonymousFunction = true)
return unionString
}

if (typeTree.kind === 'intersection') {
return typeTree.types.map(t => stringifyTypeTree(t)).join(' & ')
}

if (typeTree.kind === 'object') {
let propertiesString = typeTree.properties.map(p => {
const readonly = (p.readonly) ? 'readonly ' : ''
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:

deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==

define-data-property@^1.0.1, define-data-property@^1.1.4:
Expand Down Expand Up @@ -1160,7 +1160,7 @@ import-fresh@^3.2.1:

imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==

inflight@^1.0.4:
Expand Down Expand Up @@ -1836,9 +1836,9 @@ typed-array-length@^1.0.6:
possible-typed-array-names "^1.0.0"

typescript@^5.3.3, typescript@^5.4.5:
version "5.4.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz"
integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
version "5.7.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==

unbox-primitive@^1.0.2:
version "1.0.2"
Expand Down

0 comments on commit a587ff1

Please sign in to comment.