@@ -16,12 +16,15 @@ export class AST {
16
16
[ module_name : string ] : Map < string , { range : Range , local : string } >
17
17
} = { } ;
18
18
private uri : string ;
19
+ private prependLines : number ;
19
20
private diagnostics : Diagnostic [ ] = [ ] ;
20
21
// Array of callbacks to call once parsing is done
21
22
// Needed for things like checking if an variable name has already been declared
22
23
private diagnosticsCallbacks : Array < [ ( child : Node ) => void , Node ] > = [ ] ;
23
24
24
- constructor ( text : string , context : Context , uri : string ) {
25
+ // If prepend is supplied, prepend it to text, and offset all locations returned back to client
26
+ constructor ( text : string , context : Context , uri : string , prependLines : number = 0 ) {
27
+ this . prependLines = prependLines ;
25
28
const acornOptions : Options = {
26
29
ecmaVersion : DEFAULT_ECMA_VERSION ,
27
30
sourceType : "module" ,
@@ -327,15 +330,17 @@ export class AST {
327
330
else return undefined ;
328
331
}
329
332
330
- public getOccurences ( pos : Position ) : DocumentHighlight [ ] {
331
- const identifier = this . findIdentifierNode ( vsPosToEsPos ( pos ) ) ;
333
+ public getOccurences ( pos : Position , identifier ?: Identifier , declaration ?: DeclarationSymbol ) : Range [ ] {
334
+ if ( identifier === undefined )
335
+ identifier = this . findIdentifierNode ( vsPosToEsPos ( pos ) ) ;
332
336
if ( ! identifier ) return [ ] ;
333
337
334
- const declaration = this . findDeclarationByName ( identifier . name , identifier . loc ! ) ;
338
+ if ( declaration === undefined )
339
+ declaration = this . findDeclarationByName ( identifier . name , identifier . loc ! ) ;
335
340
if ( ! declaration ) return [ ] ;
336
341
337
342
let scopeFound = false ;
338
- const ret : DocumentHighlight [ ] = [ ] ;
343
+ const ret : Range [ ] = [ ] ;
339
344
const queue : Node [ ] = [ this . ast ] ;
340
345
341
346
while ( queue . length > 0 ) {
@@ -345,7 +350,7 @@ export class AST {
345
350
if ( ! scopeFound && node . loc && sourceLocEquals ( node . loc , declaration . scope ) )
346
351
scopeFound = true ;
347
352
if ( scopeFound && node . type === NODES . IDENTIFIER && node . name === identifier . name )
348
- ret . push ( { range : sourceLocToRange ( node . loc ! ) } )
353
+ ret . push ( sourceLocToRange ( node . loc ! ) )
349
354
350
355
getNodeChildren ( node , true ) . forEach ( node => {
351
356
if (
@@ -364,7 +369,6 @@ export class AST {
364
369
}
365
370
366
371
public getDocumentSymbols ( ) : DocumentSymbol [ ] {
367
-
368
372
let ret : DocumentSymbol [ ] = [ ]
369
373
this . declarations . forEach ( ( value , key ) => {
370
374
ret = ret . concat ( value . filter ( x => x . showInDocumentSymbols !== false ) . map ( ( declaration : DeclarationSymbol ) : DocumentSymbol => mapDeclarationSymbolToDocumentSymbol ( declaration , this . context ) ) )
@@ -374,12 +378,27 @@ export class AST {
374
378
}
375
379
376
380
public renameSymbol ( pos : Position , newName : string ) : WorkspaceEdit | null {
377
- const occurences = this . getOccurences ( pos ) ;
378
- if ( occurences . length === 0 ) return null ;
381
+ // Currently, trying to rename an imported function has two issues
382
+ // The first issue is that we should be replacing the import statement with import { <old name> as <new name> } from "<module name>";
383
+ // The second issue is that in a import specifier, there is an imported and local field.
384
+ // Imported is an identifier node for actual name of the function that was imported
385
+ // Local is also an identifier node for the alias of the function that was imported
386
+ // If the function was not renamed, both imported and local will have the same location
387
+ // This leads to an error in the client, as there are two changes to the program that have overlapping ranges
388
+ // For now, if the name that the user is trying to rename is an imported name, we don't allow renames
389
+ // I will leave this for a future CP3108 student to fix :)
390
+ const identifier = this . findIdentifierNode ( vsPosToEsPos ( pos ) ) ;
391
+ if ( ! identifier ) return null ;
392
+
393
+ const declaration = this . findDeclarationByName ( identifier . name , identifier . loc ! ) ;
394
+ if ( ! declaration || declaration . declarationKind === DeclarationKind . KIND_IMPORT ) return null ;
395
+
396
+ const occurences = this . getOccurences ( pos , identifier , declaration ) ;
397
+ if ( occurences . length === 0 || occurences . some ( x => x . start . line < this . prependLines ) ) return null ;
379
398
380
399
return {
381
400
changes : {
382
- [ this . uri ] : occurences . map ( loc => TextEdit . replace ( loc . range , newName ) )
401
+ [ this . uri ] : occurences . map ( loc => TextEdit . replace ( loc , newName ) )
383
402
}
384
403
} ;
385
404
}
@@ -405,7 +424,8 @@ export class AST {
405
424
}
406
425
if ( value === undefined )
407
426
return null ;
408
- else return {
427
+
428
+ return {
409
429
contents : {
410
430
kind : "markdown" ,
411
431
value : value
@@ -443,33 +463,30 @@ export class AST {
443
463
return autocomplete_labels [ this . context . chapter - 1 ]
444
464
. concat ( ret )
445
465
. concat ( module_autocomplete . map ( ( item : CompletionItem ) : CompletionItem => {
446
- if ( this . imported_names [ item . data . module_name ] ) {
447
- if ( this . imported_names [ item . data . module_name ] . has ( item . label ) ) {
466
+ // Logic for auto imports
467
+ const map = this . imported_names [ item . data . module_name ] ;
468
+ if ( map ) {
469
+ if ( map . has ( item . label ) ) {
448
470
return {
449
471
...item ,
450
- label : this . imported_names [ item . data . module_name ] . get ( item . label ) ! . local ,
472
+ label : map . get ( item . label ) ! . local ,
451
473
detail : `Imported from ${ item . data . module_name } ` ,
452
474
data : { type : AUTOCOMPLETE_TYPES . SYMBOL , ...item . data }
453
475
} ;
454
476
}
455
- else {
456
- // Not sure if the map preserves the order that names were inserted
457
- let last_imported_range : Range = { start : { line : 0 , character : 0 } , end : { line : 0 , character : 0 } } ;
458
- this . imported_names [ item . data . module_name ] . forEach ( range => {
459
- last_imported_range = findLastRange ( last_imported_range , range . range ) ;
460
- } ) ;
477
+ else if ( [ ...map ] [ map . size - 1 ] [ 1 ] . range . start . line >= this . prependLines ) {
461
478
return {
462
479
...item ,
463
480
additionalTextEdits : [
464
- TextEdit . insert ( last_imported_range . end , `, ${ item . label } ` )
481
+ TextEdit . insert ( [ ... map ] [ map . size - 1 ] [ 1 ] . range . end , `, ${ item . label } ` )
465
482
]
466
483
} ;
467
484
} ;
468
485
}
469
- else return {
486
+ return {
470
487
...item ,
471
488
additionalTextEdits : [
472
- TextEdit . insert ( { line : 0 , character : 0 } , `import { ${ item . label } } from "${ item . data . module_name } ";\n` )
489
+ TextEdit . insert ( { line : this . prependLines , character : 0 } , `import { ${ item . label } } from "${ item . data . module_name } ";\n` )
473
490
]
474
491
} ;
475
492
} ) )
0 commit comments