5
5
* Use of this source code is governed by an MIT-style license that can be
6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
- import { AST , TmplAstNode } from '@angular/compiler' ;
8
+ import { AST , TmplAstComponent , TmplAstDirective , TmplAstNode } from '@angular/compiler' ;
9
9
import { NgCompiler } from '@angular/compiler-cli/src/ngtsc/core' ;
10
10
import { absoluteFrom } from '@angular/compiler-cli/src/ngtsc/file_system' ;
11
11
import { MetaKind , PipeMeta } from '@angular/compiler-cli/src/ngtsc/metadata' ;
12
12
import { PerfPhase } from '@angular/compiler-cli/src/ngtsc/perf' ;
13
- import { SymbolKind , TemplateTypeChecker } from '@angular/compiler-cli/src/ngtsc/typecheck/api' ;
13
+ import {
14
+ SelectorlessComponentSymbol ,
15
+ SelectorlessDirectiveSymbol ,
16
+ SymbolKind ,
17
+ TemplateTypeChecker ,
18
+ } from '@angular/compiler-cli/src/ngtsc/typecheck/api' ;
14
19
import ts from 'typescript' ;
15
20
16
21
import {
17
22
convertToTemplateDocumentSpan ,
18
23
FilePosition ,
19
24
getParentClassMeta ,
20
25
getRenameTextAndSpanAtPosition ,
26
+ getSelectorlessTemplateSpanFromTcbLocations ,
21
27
getTargetDetailsAtTemplatePosition ,
22
28
TemplateLocationDetails ,
23
29
} from './references_and_rename_utils' ;
@@ -96,6 +102,7 @@ enum RequestKind {
96
102
DirectFromTypeScript ,
97
103
PipeName ,
98
104
Selector ,
105
+ SelectorlessIdentifier ,
99
106
}
100
107
101
108
/** The context needed to perform a rename of a pipe name. */
@@ -127,6 +134,20 @@ interface SelectorRenameContext {
127
134
renamePosition : FilePosition ;
128
135
}
129
136
137
+ /** The context needed to perform a rename of a selectorless component/directive. */
138
+ interface SelectorlessIdentifierRenameContext {
139
+ type : RequestKind . SelectorlessIdentifier ;
140
+
141
+ /** Node defining the component/directive. */
142
+ templateNode : TmplAstComponent | TmplAstDirective ;
143
+
144
+ /** Identifier of the class defining the class. */
145
+ identifier : ts . Identifier ;
146
+
147
+ /** Location used for querying the TypeScript language service. */
148
+ renamePosition : FilePosition ;
149
+ }
150
+
130
151
interface DirectFromTypescriptRenameContext {
131
152
type : RequestKind . DirectFromTypeScript ;
132
153
@@ -151,14 +172,19 @@ type IndirectRenameContext = PipeRenameContext | SelectorRenameContext;
151
172
type RenameRequest =
152
173
| IndirectRenameContext
153
174
| DirectFromTemplateRenameContext
154
- | DirectFromTypescriptRenameContext ;
175
+ | DirectFromTypescriptRenameContext
176
+ | SelectorlessIdentifierRenameContext ;
155
177
156
178
function isDirectRenameContext (
157
179
context : RenameRequest ,
158
- ) : context is DirectFromTemplateRenameContext | DirectFromTypescriptRenameContext {
180
+ ) : context is
181
+ | DirectFromTemplateRenameContext
182
+ | DirectFromTypescriptRenameContext
183
+ | SelectorlessIdentifierRenameContext {
159
184
return (
160
185
context . type === RequestKind . DirectFromTemplate ||
161
- context . type === RequestKind . DirectFromTypeScript
186
+ context . type === RequestKind . DirectFromTypeScript ||
187
+ context . type === RequestKind . SelectorlessIdentifier
162
188
) ;
163
189
}
164
190
@@ -200,6 +226,16 @@ export class RenameBuilder {
200
226
start : renameRequest . pipeNameExpr . getStart ( ) + 1 ,
201
227
} ,
202
228
} ;
229
+ } else if ( renameRequest . type === RequestKind . SelectorlessIdentifier ) {
230
+ return {
231
+ canRename : true ,
232
+ displayName : renameRequest . identifier . text ,
233
+ fullDisplayName : renameRequest . identifier . text ,
234
+ triggerSpan : {
235
+ length : renameRequest . identifier . text . length ,
236
+ start : renameRequest . identifier . getStart ( ) ,
237
+ } ,
238
+ } ;
203
239
} else {
204
240
// TODO(atscott): Add support for other special indirect renames from typescript files.
205
241
return this . tsLS . getRenameInfo ( filePath , position ) ;
@@ -299,18 +335,30 @@ export class RenameBuilder {
299
335
300
336
for ( const location of locations ) {
301
337
if ( this . ttc . isTrackedTypeCheckFile ( absoluteFrom ( location . fileName ) ) ) {
302
- const entry = convertToTemplateDocumentSpan (
303
- location ,
304
- this . ttc ,
305
- this . compiler . getCurrentProgram ( ) ,
306
- expectedRenameText ,
307
- ) ;
308
- // There is no template node whose text matches the original rename request. Bail on
309
- // renaming completely rather than providing incomplete results.
310
- if ( entry === null ) {
311
- return null ;
338
+ 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 ) ;
347
+ }
348
+ } else {
349
+ const entry = convertToTemplateDocumentSpan (
350
+ location ,
351
+ this . ttc ,
352
+ this . compiler . getCurrentProgram ( ) ,
353
+ expectedRenameText ,
354
+ ) ;
355
+ // There is no template node whose text matches the original rename request. Bail on
356
+ // renaming completely rather than providing incomplete results.
357
+ if ( entry === null ) {
358
+ return null ;
359
+ }
360
+ entries . push ( entry ) ;
312
361
}
313
- entries . push ( entry ) ;
314
362
} else {
315
363
if ( ! isDirectRenameContext ( renameRequest ) ) {
316
364
// Discard any non-template results for non-direct renames. We should only rename
@@ -358,6 +406,17 @@ export class RenameBuilder {
358
406
return null ;
359
407
}
360
408
renameRequests . push ( renameRequest ) ;
409
+ } else if (
410
+ targetDetails . symbol . kind === SymbolKind . SelectorlessComponent ||
411
+ targetDetails . symbol . kind === SymbolKind . SelectorlessDirective
412
+ ) {
413
+ const renameRequest = this . buildSelectorlessRenameRequestFromTemplate (
414
+ targetDetails . symbol ,
415
+ ) ;
416
+ if ( renameRequest === null ) {
417
+ return null ;
418
+ }
419
+ renameRequests . push ( renameRequest ) ;
361
420
} else {
362
421
const renameRequest : RenameRequest = {
363
422
type : RequestKind . DirectFromTemplate ,
@@ -413,6 +472,40 @@ export class RenameBuilder {
413
472
} ,
414
473
} ;
415
474
}
475
+
476
+ private buildSelectorlessRenameRequestFromTemplate (
477
+ symbol : SelectorlessComponentSymbol | SelectorlessDirectiveSymbol ,
478
+ ) : SelectorlessIdentifierRenameContext | null {
479
+ if ( symbol . tsSymbol === null || symbol . tsSymbol . valueDeclaration === undefined ) {
480
+ return null ;
481
+ }
482
+
483
+ const meta = this . compiler . getMeta ( symbol . tsSymbol . valueDeclaration ) ;
484
+ if ( meta === null || meta . kind !== MetaKind . Directive ) {
485
+ return null ;
486
+ }
487
+
488
+ const nameNode = meta . ref . node . name ;
489
+ const templateName =
490
+ symbol . kind === SymbolKind . SelectorlessComponent
491
+ ? symbol . templateNode . componentName
492
+ : symbol . templateNode . name ;
493
+
494
+ // Do not rename aliased references.
495
+ if ( templateName !== nameNode . text ) {
496
+ return null ;
497
+ }
498
+
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
+ } ;
508
+ }
416
509
}
417
510
418
511
/**
@@ -444,6 +537,14 @@ function getExpectedRenameTextAndInitialRenameEntries(
444
537
textSpan : { start : pipeNameExpr . getStart ( ) + 1 , length : pipeNameExpr . getText ( ) . length - 2 } ,
445
538
} ;
446
539
entries . push ( entry ) ;
540
+ } else if ( renameRequest . type === RequestKind . SelectorlessIdentifier ) {
541
+ const { identifier} = renameRequest ;
542
+ expectedRenameText = identifier . text ;
543
+ const entry : ts . RenameLocation = {
544
+ fileName : identifier . getSourceFile ( ) . fileName ,
545
+ textSpan : { start : identifier . getStart ( ) , length : identifier . getWidth ( ) } ,
546
+ } ;
547
+ entries . push ( entry ) ;
447
548
} else {
448
549
// TODO(atscott): Implement other types of special renames
449
550
return null ;
0 commit comments