Skip to content

Commit a0a09bc

Browse files
committed
fix: fix filtering jsx elements in completions for react ts 5.0
1 parent ad4288e commit a0a09bc

File tree

1 file changed

+61
-30
lines changed

1 file changed

+61
-30
lines changed

typescript/src/completions/filterJsxComponents.ts

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { GetConfig } from '../types'
2+
import { getFullTypeChecker } from '../utils'
23

3-
const reactTypesPath = 'node_modules/@types/react/index.d.ts'
4+
const reactTypesPath = 'node_modules/@types/react/'
45

56
const symbolCache = new Map<string, boolean>()
67

@@ -53,24 +54,6 @@ export default (entries: ts.CompletionEntry[], node: ts.Node, position: number,
5354
timings[`${name}Count`] ??= 0
5455
timings[`${name}Count`]++
5556
}
56-
const getIsJsxComponentSignature = (signature: ts.Signature) => {
57-
let returnType: ts.Type | undefined = signature.getReturnType()
58-
if (!returnType) return
59-
// todo setting to allow any!
60-
if (returnType.flags & ts.TypeFlags.Any) return false
61-
returnType = getPossiblyJsxType(returnType)
62-
if (!returnType) return false
63-
startMark()
64-
// todo(perf) this seems to be taking a lot of time (mui test 180ms)
65-
const typeString = typeChecker.typeToString(returnType)
66-
addMark('stringType')
67-
// todo-low resolve indentifier instead
68-
// or compare node name from decl (invest perf)
69-
if (['Element', 'ReactElement'].every(s => !typeString.startsWith(s))) return
70-
const declFile = returnType.getSymbol()?.declarations?.[0]?.getSourceFile().fileName
71-
if (!declFile?.endsWith(reactTypesPath)) return
72-
return true
73-
}
7457
const getIsEntryReactComponent = (entry: ts.CompletionEntry) => {
7558
// todo add more checks from ref https://github.com/microsoft/TypeScript/blob/e4816ed44cf9bcfe7cebb997b1f44cdb5564dac4/src/compiler/checker.ts#L30030
7659
// todo support classes
@@ -112,7 +95,7 @@ export default (entries: ts.CompletionEntry[], node: ts.Node, position: number,
11295
// startMark()
11396
const signatures = typeChecker.getSignaturesOfType(entryType, ts.SignatureKind.Call)
11497
// addMark('signatures')
115-
const result = signatures.length > 0 && signatures.every(signature => getIsJsxComponentSignature(signature))
98+
const result = isJsxElement(typeChecker, signatures, entryType)
11699
if (shouldBeCached) symbolCache.set(symbolSerialized, result)
117100
return result
118101
}
@@ -162,16 +145,30 @@ const isJsxOpeningElem = (position: number, node: ts.Node) => {
162145
return false
163146
}
164147

165-
const getReactElementType = (program: ts.Program) => {
166-
const reactDeclSource = program.getSourceFiles().find(name => name.fileName.endsWith(reactTypesPath))
167-
const namespace = reactDeclSource && ts.forEachChild(reactDeclSource, s => ts.isModuleDeclaration(s) && s.name.text === 'React' && s)
168-
if (!namespace || !namespace.body) return
169-
return ts.forEachChild(namespace.body, node => {
170-
if (ts.isInterfaceDeclaration(node) && node.name.text === 'ReactElement') {
171-
return node
172-
}
173-
return undefined
174-
})
148+
const isJsxElement = (typeChecker: ts.TypeChecker, signatures: readonly ts.Signature[], type: ts.Type) => {
149+
if (signatures.length > 0 && signatures.every(signature => getIsJsxComponentSignature(typeChecker, signature))) return true
150+
// allow pattern: const Component = condition ? 'div' : 'a'
151+
if (type.isUnion() && type.types.every(type => type.isStringLiteral())) return true
152+
return false
153+
}
154+
155+
const getIsJsxComponentSignature = (typeChecker: ts.TypeChecker, signature: ts.Signature) => {
156+
let returnType: ts.Type | undefined = signature.getReturnType()
157+
if (!returnType) return
158+
// todo setting to allow any
159+
if (returnType.flags & ts.TypeFlags.Any) return false
160+
returnType = getPossiblyJsxType(returnType)
161+
if (!returnType) return false
162+
// startMark()
163+
// todo(perf) this seems to be taking a lot of time (mui test 180ms)
164+
const typeString = typeChecker.typeToString(returnType)
165+
// addMark('stringType')
166+
// todo-low resolve indentifier instead
167+
// or compare node name from decl (invest perf)
168+
if (['Element', 'ReactElement'].every(s => !typeString.startsWith(s))) return
169+
const declFile = returnType.getSymbol()?.declarations?.[0]?.getSourceFile().fileName
170+
if (!declFile?.includes(reactTypesPath)) return
171+
return true
175172
}
176173

177174
const getPossiblyJsxType = (type: ts.Type) => {
@@ -188,3 +185,37 @@ const getPossiblyJsxType = (type: ts.Type) => {
188185
}
189186
return type.flags & ts.TypeFlags.Object ? type : undefined
190187
}
188+
189+
const getGlobalJsxElementType = (program: ts.Program) => {
190+
const checker = getFullTypeChecker(program.getTypeChecker())
191+
const globalJsxNamespace = checker.resolveName('JSX', undefined, ts.SymbolFlags.Namespace, false)
192+
if (!globalJsxNamespace) return
193+
const exportsSymbols = checker.getExportsOfModule(globalJsxNamespace)
194+
const symbolTable = tsFull.createSymbolTable(exportsSymbols)
195+
const elementSymbol = getSymbol(checker, symbolTable, 'Element', ts.SymbolFlags.Type)
196+
if (!elementSymbol) return
197+
return checker.getDeclaredTypeOfSymbol(elementSymbol)
198+
}
199+
200+
function getSymbol(
201+
checker: import('typescript-full').TypeChecker,
202+
symbols: import('typescript-full').SymbolTable,
203+
name: string,
204+
meaning: ts.SymbolFlags,
205+
): import('typescript-full').Symbol | undefined {
206+
if (meaning) {
207+
const symbol = checker.getMergedSymbol(symbols.get(name as ts.__String)!)
208+
if (symbol) {
209+
if (symbol.flags & meaning) {
210+
return symbol
211+
}
212+
if (symbol.flags & ts.SymbolFlags.Alias) {
213+
const target = checker.getAliasedSymbol(symbol)
214+
if (checker.isUnknownSymbol(target) || target.flags & meaning) {
215+
return symbol
216+
}
217+
}
218+
}
219+
}
220+
return undefined
221+
}

0 commit comments

Comments
 (0)