@@ -12,12 +12,17 @@ export const NG_CORE_PACKAGE_NAME = '@angular/core';
1212export const CUSTOM_TS_PLUGIN_PATH = './tsPlugin' ;
1313export const CUSTOM_TS_PLUGIN_NAME = 'igx-ts-plugin' ;
1414
15- enum SynaxTokens {
15+ enum SyntaxTokens {
1616 ClosingParenthesis = ')' ,
1717 MemberAccess = '.' ,
1818 Question = '?'
1919}
2020
21+ export class MemberInfo implements Pick < tss . DefinitionInfo , 'name' | 'fileName' > {
22+ public name : string ;
23+ public fileName : string ;
24+ }
25+
2126/** Returns a source file */
2227// export function getFileSource(sourceText: string): ts.SourceFile {
2328// return ts.createSourceFile('', sourceText, ts.ScriptTarget.Latest, true);
@@ -45,7 +50,9 @@ export const getIdentifierPositions = (source: string | ts.SourceFile, name: str
4550 return false ;
4651 }
4752 }
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 ;
4956 } ;
5057
5158 const findIdentifiers = ( node : ts . Node ) => {
@@ -251,7 +258,7 @@ const getTypeDefinitions = (langServ: tss.LanguageService, entryPath: string, po
251258 * @param position Index of identifier
252259 */
253260export 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 => {
255262 const definition = langServ . getDefinitionAndBoundSpan ( entryPath , position ) ?. definitions [ 0 ] ;
256263 if ( ! definition ) {
257264 return null ;
@@ -264,6 +271,19 @@ export const getTypeDefinitionAtPosition =
264271 if ( definition . kind . toString ( ) === 'method' ) {
265272 return getMethodTypeDefinition ( langServ , definition ) ;
266273 }
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+ }
267287
268288 let typeDefs = getTypeDefinitions ( langServ , definition . fileName || entryPath , definition . textSpan . start ) ;
269289 // 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 =
311331 return null ;
312332 } ;
313333
314-
315334/**
316335 * Determines if a member belongs to a type in the `igniteui-angular` toolkit.
317336 *
@@ -325,7 +344,7 @@ export const isMemberIgniteUI =
325344 const content = langServ . getProgram ( ) . getSourceFile ( entryPath ) . getText ( ) ;
326345 matchPosition = shiftMatchPosition ( matchPosition , content ) ;
327346 const prevChar = content . substr ( matchPosition - 1 , 1 ) ;
328- if ( prevChar === SynaxTokens . ClosingParenthesis ) {
347+ if ( prevChar === SyntaxTokens . ClosingParenthesis ) {
329348 // methodCall().identifier
330349 matchPosition = langServ . getBraceMatchingAtPosition ( entryPath , matchPosition - 1 ) [ 0 ] ?. start ?? matchPosition ;
331350 }
@@ -339,6 +358,49 @@ export const isMemberIgniteUI =
339358 && change . definedIn . indexOf ( typeDef . name ) !== - 1 ;
340359 } ;
341360
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+
342404/**
343405 * Shifts the match position of the identifier to the left
344406 * until any character other than an empty string or a '.' is reached. #9347
@@ -347,8 +409,8 @@ const shiftMatchPosition = (matchPosition: number, content: string): number => {
347409 do {
348410 matchPosition -- ;
349411 } 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 ) ;
352414 return matchPosition ;
353415} ;
354416
@@ -358,8 +420,7 @@ const shiftMatchPosition = (matchPosition: number, content: string): number => {
358420 * @param langServ The TypeScript LanguageService.
359421 * @param definition The method definition.
360422 */
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 => {
363424 // TODO: use typechecker for all the things?
364425 const sourceFile = langServ . getProgram ( ) . getSourceFile ( definition . fileName ) ;
365426
@@ -390,7 +451,7 @@ const getMethodTypeDefinition = (langServ: tss.LanguageService, definition: tss.
390451 // there should never be a case where a type is declared in more than one file
391452 /**
392453 * 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
394455 * the TypeChecker ignores null and undefined and returns only T which is not
395456 * marked as a union or intersection type.
396457 *
0 commit comments