@@ -12,12 +12,17 @@ export const NG_CORE_PACKAGE_NAME = '@angular/core';
12
12
export const CUSTOM_TS_PLUGIN_PATH = './tsPlugin' ;
13
13
export const CUSTOM_TS_PLUGIN_NAME = 'igx-ts-plugin' ;
14
14
15
- enum SynaxTokens {
15
+ enum SyntaxTokens {
16
16
ClosingParenthesis = ')' ,
17
17
MemberAccess = '.' ,
18
18
Question = '?'
19
19
}
20
20
21
+ export class MemberInfo implements Pick < tss . DefinitionInfo , 'name' | 'fileName' > {
22
+ public name : string ;
23
+ public fileName : string ;
24
+ }
25
+
21
26
/** Returns a source file */
22
27
// export function getFileSource(sourceText: string): ts.SourceFile {
23
28
// return ts.createSourceFile('', sourceText, ts.ScriptTarget.Latest, true);
@@ -45,7 +50,9 @@ export const getIdentifierPositions = (source: string | ts.SourceFile, name: str
45
50
return false ;
46
51
}
47
52
}
48
- return node . text === name ;
53
+ // for methods the node.text will not contain characters like ()
54
+ const cleanName = name . match ( / \w + / g) [ 0 ] || name ;
55
+ return node . text === cleanName ;
49
56
} ;
50
57
51
58
const findIdentifiers = ( node : ts . Node ) => {
@@ -251,7 +258,7 @@ const getTypeDefinitions = (langServ: tss.LanguageService, entryPath: string, po
251
258
* @param position Index of identifier
252
259
*/
253
260
export const getTypeDefinitionAtPosition =
254
- ( langServ : tss . LanguageService , entryPath : string , position : number ) : Pick < tss . DefinitionInfo , 'name' | 'fileName' > | null => {
261
+ ( langServ : tss . LanguageService , entryPath : string , position : number ) : MemberInfo | null => {
255
262
const definition = langServ . getDefinitionAndBoundSpan ( entryPath , position ) ?. definitions [ 0 ] ;
256
263
if ( ! definition ) {
257
264
return null ;
@@ -264,6 +271,19 @@ export const getTypeDefinitionAtPosition =
264
271
if ( definition . kind . toString ( ) === 'method' ) {
265
272
return getMethodTypeDefinition ( langServ , definition ) ;
266
273
}
274
+ if ( entryPath . endsWith ( '.ts' ) ) {
275
+ // for ts files we can use the type checker to look up a specific node
276
+ // and attempt to resolve its actual type
277
+ const sourceFile = langServ . getProgram ( ) . getSourceFile ( entryPath ) ;
278
+ // const node = (tss as any).getTouchingPropertyName(sourceFile, position); -> tss internal that looks up a node
279
+ const node = findNodeAtPosition ( sourceFile , position ) ;
280
+ if ( node ) {
281
+ const memberInfo = resolveMemberInfo ( langServ , node ) ;
282
+ if ( memberInfo ) {
283
+ return memberInfo ;
284
+ }
285
+ }
286
+ }
267
287
268
288
let typeDefs = getTypeDefinitions ( langServ , definition . fileName || entryPath , definition . textSpan . start ) ;
269
289
// if there are no type definitions found, the identifier is a ts property, referred in an internal/external template
@@ -311,7 +331,6 @@ export const getTypeDefinitionAtPosition =
311
331
return null ;
312
332
} ;
313
333
314
-
315
334
/**
316
335
* Determines if a member belongs to a type in the `igniteui-angular` toolkit.
317
336
*
@@ -325,7 +344,7 @@ export const isMemberIgniteUI =
325
344
const content = langServ . getProgram ( ) . getSourceFile ( entryPath ) . getText ( ) ;
326
345
matchPosition = shiftMatchPosition ( matchPosition , content ) ;
327
346
const prevChar = content . substr ( matchPosition - 1 , 1 ) ;
328
- if ( prevChar === SynaxTokens . ClosingParenthesis ) {
347
+ if ( prevChar === SyntaxTokens . ClosingParenthesis ) {
329
348
// methodCall().identifier
330
349
matchPosition = langServ . getBraceMatchingAtPosition ( entryPath , matchPosition - 1 ) [ 0 ] ?. start ?? matchPosition ;
331
350
}
@@ -339,6 +358,49 @@ export const isMemberIgniteUI =
339
358
&& change . definedIn . indexOf ( typeDef . name ) !== - 1 ;
340
359
} ;
341
360
361
+ const resolveMemberInfo = ( langServ : tss . LanguageService , node : tss . Node ) : MemberInfo | null => {
362
+ const typeChecker = langServ . getProgram ( ) . getTypeChecker ( ) ;
363
+ const nodeType = typeChecker . getTypeAtLocation ( node ) ;
364
+ const typeArguments = typeChecker . getTypeArguments ( nodeType as tss . TypeReference ) ;
365
+ if ( typeArguments && typeArguments . length < 1 ) {
366
+ // it's not a generic type so try to look up its name and fileName
367
+ // atm we do not support migrating union/intersection generic types
368
+ // a type symbol (type) should have only one declaration
369
+ // if the type is 'any' or 'some', there will be no type symbol
370
+ const name = nodeType . getSymbol ( ) ?. getName ( ) ;
371
+ const declarations = nodeType . getSymbol ( ) ?. getDeclarations ( ) ;
372
+ if ( declarations && declarations . length > 0 ) {
373
+ const fileName = declarations [ 0 ] . getSourceFile ( ) . fileName ;
374
+ if ( name && fileName ) {
375
+ return { name, fileName } ;
376
+ }
377
+ }
378
+ }
379
+
380
+ return null ;
381
+ }
382
+
383
+ /**
384
+ * Looks up a node which end property matches the specified position.
385
+ * Can go to the next node if the currently found one is invalid (comment for example)
386
+ */
387
+ const findNodeAtPosition = ( sourceFile : tss . SourceFile , position : number ) : tss . Node | null => {
388
+ if ( ! sourceFile ) {
389
+ return null ;
390
+ }
391
+
392
+ return findInnerNode ( sourceFile , position ) ;
393
+ }
394
+ const findInnerNode = ( node : tss . Node , position : number ) : tss . Node | null => {
395
+ if ( position <= node . getEnd ( ) ) {
396
+ // see tss.forEachChild for documentation
397
+ // look for the innermost child that matches the position
398
+ return node . forEachChild ( cn => findInnerNode ( cn , position ) ) || node ;
399
+ }
400
+
401
+ return null ;
402
+ }
403
+
342
404
/**
343
405
* Shifts the match position of the identifier to the left
344
406
* until any character other than an empty string or a '.' is reached. #9347
@@ -347,8 +409,8 @@ const shiftMatchPosition = (matchPosition: number, content: string): number => {
347
409
do {
348
410
matchPosition -- ;
349
411
} while ( matchPosition > 0 && ! content [ matchPosition - 1 ] . trim ( )
350
- || content [ matchPosition - 1 ] === SynaxTokens . MemberAccess
351
- || content [ matchPosition - 1 ] === SynaxTokens . Question ) ;
412
+ || content [ matchPosition - 1 ] === SyntaxTokens . MemberAccess
413
+ || content [ matchPosition - 1 ] === SyntaxTokens . Question ) ;
352
414
return matchPosition ;
353
415
} ;
354
416
@@ -358,8 +420,7 @@ const shiftMatchPosition = (matchPosition: number, content: string): number => {
358
420
* @param langServ The TypeScript LanguageService.
359
421
* @param definition The method definition.
360
422
*/
361
- const getMethodTypeDefinition = ( langServ : tss . LanguageService , definition : tss . DefinitionInfo ) :
362
- Pick < tss . DefinitionInfo , 'name' | 'fileName' > | null => {
423
+ const getMethodTypeDefinition = ( langServ : tss . LanguageService , definition : tss . DefinitionInfo ) : MemberInfo | null => {
363
424
// TODO: use typechecker for all the things?
364
425
const sourceFile = langServ . getProgram ( ) . getSourceFile ( definition . fileName ) ;
365
426
@@ -390,7 +451,7 @@ const getMethodTypeDefinition = (langServ: tss.LanguageService, definition: tss.
390
451
// there should never be a case where a type is declared in more than one file
391
452
/**
392
453
* For union return types like T | null | undefined
393
- * and interesection return types like T & null & undefined
454
+ * and intersection return types like T & null & undefined
394
455
* the TypeChecker ignores null and undefined and returns only T which is not
395
456
* marked as a union or intersection type.
396
457
*
0 commit comments