55 * Use of this source code is governed by an MIT-style license that can be
66 * found in the LICENSE file at https://angular.dev/license
77 */
8- import { AST , TmplAstComponent , TmplAstDirective , TmplAstNode } from '@angular/compiler' ;
8+ import { AST , TmplAstComponent , TmplAstNode } from '@angular/compiler' ;
99import { NgCompiler } from '@angular/compiler-cli/src/ngtsc/core' ;
1010import { absoluteFrom } from '@angular/compiler-cli/src/ngtsc/file_system' ;
11- import { MetaKind , PipeMeta } from '@angular/compiler-cli/src/ngtsc/metadata' ;
11+ import { MetaKind , PipeMeta , DirectiveMeta } from '@angular/compiler-cli/src/ngtsc/metadata' ;
1212import { PerfPhase } from '@angular/compiler-cli/src/ngtsc/perf' ;
13- import {
14- SelectorlessComponentSymbol ,
15- SelectorlessDirectiveSymbol ,
16- SymbolKind ,
17- TemplateTypeChecker ,
18- } from '@angular/compiler-cli/src/ngtsc/typecheck/api' ;
13+ import { SymbolKind , TemplateTypeChecker } from '@angular/compiler-cli/src/ngtsc/typecheck/api' ;
1914import ts from 'typescript' ;
2015
2116import {
2217 convertToTemplateDocumentSpan ,
2318 FilePosition ,
2419 getParentClassMeta ,
2520 getRenameTextAndSpanAtPosition ,
26- getSelectorlessTemplateSpanFromTcbLocations ,
2721 getTargetDetailsAtTemplatePosition ,
22+ SelectorlessCollector ,
2823 TemplateLocationDetails ,
2924} from './references_and_rename_utils' ;
3025import { collectMemberMethods , findTightestNode } from './utils/ts_utils' ;
@@ -138,9 +133,6 @@ interface SelectorRenameContext {
138133interface SelectorlessIdentifierRenameContext {
139134 type : RequestKind . SelectorlessIdentifier ;
140135
141- /** Node defining the component/directive. */
142- templateNode : TmplAstComponent | TmplAstDirective ;
143-
144136 /** Identifier of the class defining the class. */
145137 identifier : ts . Identifier ;
146138
@@ -311,7 +303,7 @@ export class RenameBuilder {
311303 return allRenameLocations . length > 0 ? allRenameLocations : null ;
312304 }
313305
314- findRenameLocationsAtTypescriptPosition (
306+ private findRenameLocationsAtTypescriptPosition (
315307 renameRequest : RenameRequest ,
316308 ) : readonly ts . RenameLocation [ ] | null {
317309 return this . compiler . perfRecorder . inPhase ( PerfPhase . LsReferencesAndRenames , ( ) => {
@@ -336,15 +328,11 @@ export class RenameBuilder {
336328 for ( const location of locations ) {
337329 if ( this . ttc . isTrackedTypeCheckFile ( absoluteFrom ( location . fileName ) ) ) {
338330 if ( renameRequest . type === RequestKind . SelectorlessIdentifier ) {
339- const selectorlessEntries = getSelectorlessTemplateSpanFromTcbLocations (
340- location ,
341- this . ttc ,
342- this . compiler . getCurrentProgram ( ) ,
343- renameRequest . templateNode ,
344- ) ;
345- if ( selectorlessEntries !== null ) {
346- entries . push ( ...selectorlessEntries ) ;
331+ const selectorlessEntries = this . getSelectorlessRenameLocations ( renameRequest ) ;
332+ if ( selectorlessEntries === null ) {
333+ return null ;
347334 }
335+ entries . push ( ...selectorlessEntries ) ;
348336 } else {
349337 const entry = convertToTemplateDocumentSpan (
350338 location ,
@@ -410,13 +398,15 @@ export class RenameBuilder {
410398 targetDetails . symbol . kind === SymbolKind . SelectorlessComponent ||
411399 targetDetails . symbol . kind === SymbolKind . SelectorlessDirective
412400 ) {
413- const renameRequest = this . buildSelectorlessRenameRequestFromTemplate (
414- targetDetails . symbol ,
415- ) ;
416- if ( renameRequest === null ) {
401+ const tsSymbol = targetDetails . symbol . tsSymbol ;
402+ const meta =
403+ tsSymbol === null || tsSymbol . valueDeclaration === undefined
404+ ? null
405+ : this . compiler . getMeta ( tsSymbol . valueDeclaration ) ;
406+ if ( meta === null || meta . kind !== MetaKind . Directive ) {
417407 return null ;
418408 }
419- renameRequests . push ( renameRequest ) ;
409+ renameRequests . push ( this . buildSelectorlessRenameRequest ( meta ) ) ;
420410 } else {
421411 const renameRequest : RenameRequest = {
422412 type : RequestKind . DirectFromTemplate ,
@@ -440,11 +430,16 @@ export class RenameBuilder {
440430 return null ;
441431 }
442432 const meta = getParentClassMeta ( requestNode , this . compiler ) ;
443- if ( meta !== null && meta . kind === MetaKind . Pipe && meta . nameExpr === requestNode ) {
433+
434+ if ( meta ?. kind === MetaKind . Pipe && meta . nameExpr === requestNode ) {
444435 return this . buildPipeRenameRequest ( meta ) ;
445- } else {
446- return { type : RequestKind . DirectFromTypeScript , requestNode} ;
447436 }
437+
438+ if ( meta ?. kind === MetaKind . Directive && meta . ref . node . name === requestNode ) {
439+ return this . buildSelectorlessRenameRequest ( meta ) ;
440+ }
441+
442+ return { type : RequestKind . DirectFromTypeScript , requestNode} ;
448443 }
449444
450445 private buildPipeRenameRequest ( meta : PipeMeta ) : PipeRenameContext | null {
@@ -473,38 +468,100 @@ export class RenameBuilder {
473468 } ;
474469 }
475470
476- private buildSelectorlessRenameRequestFromTemplate (
477- symbol : SelectorlessComponentSymbol | SelectorlessDirectiveSymbol ,
478- ) : SelectorlessIdentifierRenameContext | null {
479- if ( symbol . tsSymbol === null || symbol . tsSymbol . valueDeclaration === undefined ) {
480- return null ;
481- }
471+ private buildSelectorlessRenameRequest ( meta : DirectiveMeta ) : SelectorlessIdentifierRenameContext {
472+ const identifier = meta . ref . node . name ;
473+
474+ return {
475+ type : RequestKind . SelectorlessIdentifier ,
476+ identifier,
477+ renamePosition : {
478+ fileName : identifier . getSourceFile ( ) . fileName ,
479+ position : identifier . getStart ( ) ,
480+ } ,
481+ } ;
482+ }
482483
483- const meta = this . compiler . getMeta ( symbol . tsSymbol . valueDeclaration ) ;
484- if ( meta === null || meta . kind !== MetaKind . Directive ) {
484+ /** Gets the rename locations for a selectorless request. */
485+ private getSelectorlessRenameLocations (
486+ request : SelectorlessIdentifierRenameContext ,
487+ ) : ts . RenameLocation [ ] | null {
488+ // Find all the references to the class.
489+ const refs = this . tsLS . getReferencesAtPosition (
490+ request . renamePosition . fileName ,
491+ request . renamePosition . position ,
492+ ) ;
493+
494+ if ( refs === undefined ) {
485495 return null ;
486496 }
487497
488- const nameNode = meta . ref . node . name ;
489- const templateName =
490- symbol . kind === SymbolKind . SelectorlessComponent
491- ? symbol . templateNode . componentName
492- : symbol . templateNode . name ;
498+ const entries : ts . RenameLocation [ ] = [ ] ;
499+ let hasSelectorlessReferences = false ;
493500
494- // Do not rename aliased references.
495- if ( templateName !== nameNode . text ) {
496- return null ;
501+ for ( const ref of refs ) {
502+ // Preserve the TS-based references.
503+ if ( ! this . ttc . isTrackedTypeCheckFile ( absoluteFrom ( ref . fileName ) ) ) {
504+ entries . push ( ref ) ;
505+ continue ;
506+ }
507+
508+ // Resolve the TCB references to their real locations.
509+ const entry = convertToTemplateDocumentSpan ( ref , this . ttc , this . compiler . getCurrentProgram ( ) ) ;
510+ const typeCheckInfo =
511+ entry === null
512+ ? undefined
513+ : getTypeCheckInfoAtPosition ( entry . fileName , entry . textSpan . start , this . compiler ) ;
514+
515+ if ( entry === null || typeCheckInfo === undefined ) {
516+ continue ;
517+ }
518+
519+ const nodes = SelectorlessCollector . getSelectorlessNodes ( typeCheckInfo . nodes ) ;
520+
521+ // Go through all the selectorless template nodes and look for matches.
522+ for ( const node of nodes ) {
523+ const startSpan = node . startSourceSpan ;
524+ const isComponent = node instanceof TmplAstComponent ;
525+ const name = isComponent ? node . componentName : node . name ;
526+
527+ if (
528+ // The span of the template node should match the span of the reference.
529+ startSpan . start . offset !== entry . textSpan . start ||
530+ startSpan . end . offset !== entry . textSpan . start + entry . textSpan . length ||
531+ // Skip aliased directives.
532+ name !== request . identifier . text
533+ ) {
534+ continue ;
535+ }
536+
537+ hasSelectorlessReferences = true ;
538+
539+ entries . push ( {
540+ fileName : entry . fileName ,
541+ textSpan : {
542+ // +1 to skip over the `<` for components and `@` for directives.
543+ start : entry . textSpan . start + 1 ,
544+ length : name . length ,
545+ } ,
546+ } ) ;
547+
548+ // Components also need to rename the closing tag.
549+ if ( isComponent && ! node . isSelfClosing && node . endSourceSpan !== null ) {
550+ entries . push ( {
551+ fileName : entry . fileName ,
552+ textSpan : {
553+ // +2 to skip over the `</` of the closing tag.
554+ start : node . endSourceSpan . start . offset + 2 ,
555+ length : name . length ,
556+ } ,
557+ } ) ;
558+ }
559+ }
497560 }
498561
499- return {
500- type : RequestKind . SelectorlessIdentifier ,
501- templateNode : symbol . templateNode ,
502- identifier : nameNode ,
503- renamePosition : {
504- fileName : meta . ref . node . getSourceFile ( ) . fileName ,
505- position : nameNode . getStart ( ) ,
506- } ,
507- } ;
562+ // Do not produce any rename locations if there weren't any references in the template.
563+ // This is for backwards compatibility since we should fall back to the TS language service.
564+ return hasSelectorlessReferences ? entries : null ;
508565 }
509566}
510567
0 commit comments