Skip to content

Possible incorrect getDocumentHighlights behavior with global lexically scoped variables #4560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jkillian opened this issue Aug 31, 2015 · 6 comments
Assignees
Labels
API Relates to the public API for TypeScript External Relates to another program, environment, or user action which we cannot control.

Comments

@jkillian
Copy link

There seem to be some related funny issues going on. When calling getDocumentHighlights on the position of the hello variable declaration in the following code snippet:

// Incorrect case:

class HelloWorld {
    constructor(public name: string) { }
    sayHello() {
        return `Hello, ${this.name}!`;
    }
};

let hello = new HelloWorld("TSLint");
hello.sayHello();

only the declaration is returned and not the usage on the next line. However, in the following code snippet, both uses are returned, correctly:

// Correct case:

class HelloWorld {
    constructor(public name: string) { }
    sayHello() {
        return `Hello, ${this.name}!`;
    }
};

if (true) {
    let hello = new HelloWorld("TSLint");
    hello.sayHello();
}
// Correct case (var vs. let):

class HelloWorld {
    constructor(public name: string) { }
    sayHello() {
        return `Hello, ${this.name}!`;
    }
};

var hello = new HelloWorld("TSLint");
hello.sayHello();

Also strangely, Sublime will correctly show both references in all three cases.

I've been trying to figure out the root cause but haven't been able to yet. It seems that the failure occurs specifically for lexically scoped variables declared at global scope, such as let declarations at global scope.

In case it helps, I did notice that in the getReferencesInNode function the array of possible positions contains exactly the two actual positions in both the incorrect and correct case. However, there is a difference when finding the relatedSymbol:

var referenceSymbol = typeChecker.getSymbolAtLocation(referenceLocation);
if (referenceSymbol) {
    var referenceSymbolDeclaration = referenceSymbol.valueDeclaration;
    var shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration);
    var relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation);
    // NOTE (added for the Github issue):
    // relatedSymbol is undefined here in incorrect case, defined in the correct case
    if (relatedSymbol) {
        var referencedSymbol = getReferencedSymbol(relatedSymbol);
        referencedSymbol.references.push(getReferenceEntryFromNode(referenceLocation));
    }
    else if (!(referenceSymbol.flags & 67108864 /* Transient */) && searchSymbols.indexOf(shorthandValueSymbol) >= 0) {
        var referencedSymbol = getReferencedSymbol(shorthandValueSymbol);
        referencedSymbol.references.push(getReferenceEntryFromNode(referenceSymbolDeclaration.name));
    }
}

(Related to palantir/tslint#570)

@mhegazy mhegazy added Bug A bug in TypeScript API Relates to the public API for TypeScript labels Aug 31, 2015
@mhegazy mhegazy added this to the TypeScript 1.7 milestone Aug 31, 2015
@jkillian
Copy link
Author

@DanielRosenwasser Figured out the problem I believe, In TSLint, we constructed a language service host object as such:

    export function createLanguageServiceHost(fileName: string, source: string) {
        const host: ts.LanguageServiceHost = {
            getCompilationSettings: () => Lint.createCompilerOptions(),
            getCurrentDirectory: () => "",
            getDefaultLibFileName: () => "lib.d.ts",
            getScriptFileNames: () => [fileName],
            getScriptSnapshot: () => ts.ScriptSnapshot.fromString(source),
            getScriptVersion: () => "1",
            log: (message) => { /* */ }
        };

        return host;
    }

The issue arises here:

getScriptSnapshot: () => ts.ScriptSnapshot.fromString(source),

When the language services seem requests a snapshot for the lib file, we were returning the source for the source file which is obviously non-sensical. Strangely enough, everything still worked fine in most cases, but not all cases (see above).

Do you have a suggestion on a good way to change this?

@DanielRosenwasser
Copy link
Member

@mhegazy can check me on this, but consider just returning an empty string. That way there won't be any conflicting declarations. Let us know if that fixes the issue.

@jkillian
Copy link
Author

Thanks! This change you suggested seems to fix the issues we were having (and all our tests pass with this change):

getScriptSnapshot: name => ts.ScriptSnapshot.fromString(name === fileName ? source : ""),

So this won't cause any issues with the language services? Seems a bit hacky and just want to double-check that it's a safe change to make

@mhegazy
Copy link
Contributor

mhegazy commented Sep 11, 2015

this looks good to me. an empty lib file should do the trick.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 11, 2015

so is this issue resolved now?

@jkillian
Copy link
Author

Yes, thanks for the help. #3688 should be resolved as well, it was caused by the same issue I believe.

@DanielRosenwasser DanielRosenwasser added External Relates to another program, environment, or user action which we cannot control. and removed Bug A bug in TypeScript labels Sep 11, 2015
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
API Relates to the public API for TypeScript External Relates to another program, environment, or user action which we cannot control.
Projects
None yet
Development

No branches or pull requests

3 participants