Skip to content

Commit

Permalink
feat: support jsx components
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsdev committed Oct 29, 2022
1 parent 3538e64 commit 65e3c87
Show file tree
Hide file tree
Showing 16 changed files with 5,330 additions and 27 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@types/glob": "^8.0.0",
"@types/mocha": "^10.0.0",
"@types/node": "^18.8.0",
"@types/react": "^18.0.24",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"eslint": "^8.26.0",
Expand Down
14 changes: 11 additions & 3 deletions packages/api/src/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export const PrimitiveKindText: Record<PrimitiveKind, string> = {
unknown: "unknown",
}

export const KindText: Record<LocalizableKind | "method", string> = {
export const KindText: Record<
LocalizableKind | "method" | "jsx_component",
string
> = {
bigint_literal: "$1n",
boolean_literal: "$1",
enum_literal: "enum",
Expand All @@ -42,16 +45,20 @@ export const KindText: Record<LocalizableKind | "method", string> = {
class: "class",
interface: "interface",
method: "method",
jsx_component: "component",
}

export function getKindText(
kind: LocalizableKind,
{ insideClassOrInterface }: { insideClassOrInterface?: boolean } = {},
{
insideClassOrInterface,
isJSXElement,
}: { insideClassOrInterface?: boolean; isJSXElement?: boolean } = {},
...args: string[]
) {
return args.reduce<string>((prev, curr, i) => {
return prev.replace(new RegExp(`\\$${i + 1}`, "g"), curr)
}, KindText[kind === "function" && insideClassOrInterface ? "method" : kind])
}, KindText[kind === "function" && insideClassOrInterface ? "method" : isJSXElement ? (kind === "function" ? "jsx_component" : kind) : kind])
}

export function getPrimitiveKindText(kind: PrimitiveKind) {
Expand Down Expand Up @@ -80,6 +87,7 @@ export function localizePurpose(purpose: TypePurpose): string {
type_parameter_list: "type parameters",
type_argument_list: "type arguments",
index_parameter_type: "parameter",
jsx_properties: "props",
}

return nameByPurpose[purpose]
Expand Down
29 changes: 23 additions & 6 deletions packages/api/src/localizedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,17 @@ function getChildren(
}

case "function": {
const { signatures } = info
const { signatures, isJSXElement } = info

if (signatures.length === 1) {
return getLocalizedSignatureChildren(
signatures[0],
isJSXElement,
typeArguments
)
} else {
return signatures.map((sig) =>
getLocalizedSignature(sig, typeParameters)
getLocalizedSignature(sig, isJSXElement, typeParameters)
)
}
}
Expand Down Expand Up @@ -401,29 +402,42 @@ function getChildren(

function getLocalizedSignature(
signature: SignatureInfo,
isInsideJSXElement: boolean | undefined,
typeArguments?: TypeInfo[]
) {
const symbol = wrapSafe(localizeSymbol)(signature.symbolMeta)

return createChild({
kindText: "signature",
kindText: !isInsideJSXElement ? "signature" : "definition",
kind: "signature",
symbol,
locations: symbol?.locations,
children: getLocalizedSignatureChildren(signature, typeArguments),
children: getLocalizedSignatureChildren(
signature,
isInsideJSXElement,
typeArguments
),
})
}

function getLocalizedSignatureChildren(
signature: SignatureInfo,
isInsideJSXElement: boolean | undefined,
typeArguments?: TypeInfo[]
) {
return [
...getTypeParameterAndArgumentList(
signature.typeParameters,
typeArguments
),
...signature.parameters.map(localize),
...signature.parameters.map((p, i) =>
localizeOpts(
p,
i === 0 && isInsideJSXElement
? { purpose: "jsx_properties" }
: undefined
)
),
...(signature.returnType
? [localizeOpts(signature.returnType, { purpose: "return" })]
: []),
Expand Down Expand Up @@ -486,7 +500,10 @@ function getKind(info: ResolvedTypeInfo): string {
const kindText = (kind: LocalizableKind, ...args: string[]) =>
getKindText(
kind,
{ insideClassOrInterface: info.symbolMeta?.insideClassOrInterface },
{
insideClassOrInterface: info.symbolMeta?.insideClassOrInterface,
isJSXElement: info.kind === "function" && info.isJSXElement,
},
...args
)

Expand Down
11 changes: 11 additions & 0 deletions packages/api/src/objectUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,14 @@ export function removeDuplicates<T extends object>(arr: T[]) {
return val
})
}

/**
* @internal
*/
export function cartesianEqual<T>(
arr1: T[],
arr2: T[],
eq?: (t: T, k: T) => boolean
) {
return arr1.some((x) => arr2.some((y) => eq?.(x, y) ?? x === y))
}
13 changes: 13 additions & 0 deletions packages/api/src/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
isNonEmpty,
arrayContentsEqual,
removeDuplicates,
cartesianEqual,
} from "./objectUtil"
import {
DeclarationInfo,
Expand Down Expand Up @@ -373,6 +374,17 @@ function _generateTypeTree(
}),
}
} else if (signatures.length > 0) {
const isJSXElement = !!(
node &&
cartesianEqual(
[node.kind, node.parent?.kind],
[
ts.SyntaxKind.JsxElement,
ts.SyntaxKind.JsxSelfClosingElement,
]
)
)

return {
kind: "function",
signatures: signatures.map((s) =>
Expand All @@ -382,6 +394,7 @@ function _generateTypeTree(
typeParameters: s.typeParameters,
})
),
...(isJSXElement && { isJSXElement }),
}
} else {
return {
Expand Down
4 changes: 3 additions & 1 deletion packages/api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export type TypeInfoNoId = {
classSymbol?: SymbolInfo
indexInfos?: IndexInfo[]
}
| { kind: "function"; signatures: SignatureInfo[] }
| { kind: "function"; signatures: SignatureInfo[]; isJSXElement?: boolean }
| { kind: "array"; type: TypeInfo }
| { kind: "tuple"; types: TypeInfo[]; names?: string[] }
| {
Expand Down Expand Up @@ -193,6 +193,7 @@ export type TypePurpose =
| "type_parameter_list"
| "type_argument_list"
| "parameter_value"
| "jsx_properties"

export type PrimitiveKind = TypeInfoKind<"primitive">["primitive"]
export type LocalizableKind = Exclude<TypeInfo["kind"], "reference">
Expand Down Expand Up @@ -302,6 +303,7 @@ export type LocalizeOpts = {
typeArguments?: TypeInfo[]
typeArgument?: TypeInfo
includeIds?: boolean
isInsideJSXElement?: boolean
}

/**
Expand Down
8 changes: 5 additions & 3 deletions packages/api/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export function getNodeType(ctx: TypescriptContext, node: ts.Node) {
)(getNodeSymbol(ctx, node))
if (symbolType && isValidType(symbolType)) return symbolType

if (ts.isTypeNode(node) || ts.isTypeNode(node.parent)) {
if (ts.isTypeNode(node) || (node.parent && ts.isTypeNode(node.parent))) {
const typeNode = ts.isTypeNode(node)
? node
: ts.isTypeNode(node.parent)
Expand Down Expand Up @@ -701,16 +701,18 @@ export function getSignatureTypeArguments(

export function getDescendantAtPosition(
ctx: SourceFileTypescriptContext,
sourceFile: ts.SourceFile,
position: number
) {
return getDescendantAtRange(ctx, [position, position])
return getDescendantAtRange(ctx, sourceFile, [position, position])
}

/**
* https://github.com/dsherret/ts-ast-viewer/blob/b4be8f2234a1c3c099296bf5d0ad6cc14107367c/site/src/compiler/getDescendantAtRange.ts
*/
export function getDescendantAtRange(
{ sourceFile, ts }: SourceFileTypescriptContext,
{ ts }: TypescriptContext,
sourceFile: ts.SourceFile,
range: [number, number]
) {
let bestMatch: { node: ts.Node; start: number } = {
Expand Down
34 changes: 24 additions & 10 deletions packages/typescript-explorer-vscode/src/view/typeTreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,10 @@ export class TypeTreeItem extends vscode.TreeItem {
private provider: TypeTreeProvider,
protected parent?: TypeTreeItem
) {
const { label, description, contextValue, icon } = getMeta(typeInfo)

const depth = (parent?.depth ?? 0) + 1
const collapsibleState =
(typeInfo.children?.length ?? 0) === 0
? NoChildren
: depth === 1
? Expanded
: Collapsed

const { label, description, contextValue, icon, collapsibleState } =
getMeta(typeInfo, depth)

super(label, collapsibleState)

Expand Down Expand Up @@ -201,19 +196,35 @@ type TypeTreeItemMeta = {
description?: string
contextValue?: TypeTreeItemContextValue
icon?: vscode.ThemeIcon
collapsibleState: vscode.TreeItemCollapsibleState
}

function getMeta(info: LocalizedTypeInfo): TypeTreeItemMeta {
function getMeta(info: LocalizedTypeInfo, depth: number): TypeTreeItemMeta {
let nameOverridden = false

const label = getLabel()
const description = getDescription()

const collapsibleState = getCollapsibleState()

return {
label,
description,
contextValue: getContextValue(),
icon: getIcon(),
collapsibleState,
}

function getCollapsibleState() {
if ((info.children?.length ?? 0) === 0) {
return NoChildren
}

if (info.purpose === "jsx_properties") {
return Expanded
}

return depth === 1 ? Expanded : Collapsed
}

function getLabel() {
Expand Down Expand Up @@ -255,7 +266,10 @@ function getMeta(info: LocalizedTypeInfo): TypeTreeItemMeta {
const baseDescription = decorate(info.kindText)

const aliasDescriptionBase =
info.alias ?? (nameOverridden && info.symbol?.name)
info.alias ??
(nameOverridden &&
info.purpose !== "jsx_properties" &&
info.symbol?.name)
const aliasDescription =
aliasDescriptionBase && decorate(aliasDescriptionBase)

Expand Down
2 changes: 1 addition & 1 deletion packages/typescript-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function init(modules: {
ts: modules.typescript,
}

const node = getDescendantAtPosition(ctx, position)
const node = getDescendantAtPosition(ctx, ctx.sourceFile, position)

if (!node || node === sourceFile) {
// Avoid giving quickInfo for the sourceFile as a whole.
Expand Down
Loading

0 comments on commit 65e3c87

Please sign in to comment.