mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-22 02:35:48 -05:00
Handle localness in special cases by checking exported variable assignment (#43851)
* Handle localness in special cases by checking exported variable assignment Fixes #42976 * Fix existing tests where arrow now behaves similar to function expression * Update src/services/goToDefinition.ts
This commit is contained in:
@@ -619,7 +619,7 @@ namespace ts {
|
||||
export function getNameOfDeclaration(declaration: Declaration | Expression | undefined): DeclarationName | undefined {
|
||||
if (declaration === undefined) return undefined;
|
||||
return getNonAssignedNameOfDeclaration(declaration) ||
|
||||
(isFunctionExpression(declaration) || isClassExpression(declaration) ? getAssignedName(declaration) : undefined);
|
||||
(isFunctionExpression(declaration) || isArrowFunction(declaration) || isClassExpression(declaration) ? getAssignedName(declaration) : undefined);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
|
||||
@@ -326,10 +326,41 @@ namespace ts.GoToDefinition {
|
||||
sourceFile,
|
||||
FindAllReferences.getContextNode(declaration)
|
||||
),
|
||||
isLocal: !checker.isDeclarationVisible(declaration)
|
||||
isLocal: !isDefinitionVisible(checker, declaration)
|
||||
};
|
||||
}
|
||||
|
||||
function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean {
|
||||
if (checker.isDeclarationVisible(declaration)) return true;
|
||||
if (!declaration.parent) return false;
|
||||
|
||||
// Variable initializers are visible if variable is visible
|
||||
if (hasInitializer(declaration.parent) && declaration.parent.initializer === declaration) return isDefinitionVisible(checker, declaration.parent as Declaration);
|
||||
|
||||
// Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
// Private/protected properties/methods are not visible
|
||||
if (hasEffectiveModifier(declaration, ModifierFlags.Private)) return false;
|
||||
// Public properties/methods are visible if its parents are visible, so:
|
||||
// falls through
|
||||
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
case SyntaxKind.ShorthandPropertyAssignment:
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return isDefinitionVisible(checker, declaration.parent as Declaration);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo {
|
||||
return createDefinitionInfo(decl, typeChecker, decl.symbol, decl);
|
||||
}
|
||||
|
||||
@@ -621,6 +621,305 @@ bar();
|
||||
checkProjectActualFiles(service.configuredProjects.get(servicesConfig.path)!, [servicesFile.path, servicesConfig.path, libFile.path, typesFile.path, programFile.path]);
|
||||
});
|
||||
|
||||
describe("special handling of localness of the definitions for findAllRefs", () => {
|
||||
function setup(definition: string, usage: string) {
|
||||
const solutionLocation = "/user/username/projects/solution";
|
||||
const solution: File = {
|
||||
path: `${solutionLocation}/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
files: [],
|
||||
references: [
|
||||
{ path: "./api" },
|
||||
{ path: "./app" },
|
||||
]
|
||||
})
|
||||
};
|
||||
const apiConfig: File = {
|
||||
path: `${solutionLocation}/api/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
outDir: "dist",
|
||||
rootDir: "src",
|
||||
},
|
||||
include: ["src"],
|
||||
references: [{ path: "../shared" }]
|
||||
})
|
||||
};
|
||||
const apiFile: File = {
|
||||
path: `${solutionLocation}/api/src/server.ts`,
|
||||
content: `import * as shared from "../../shared/dist";
|
||||
${usage}`
|
||||
};
|
||||
const appConfig: File = {
|
||||
path: `${solutionLocation}/app/tsconfig.json`,
|
||||
content: apiConfig.content
|
||||
};
|
||||
const appFile: File = {
|
||||
path: `${solutionLocation}/app/src/app.ts`,
|
||||
content: apiFile.content
|
||||
};
|
||||
const sharedConfig: File = {
|
||||
path: `${solutionLocation}/shared/tsconfig.json`,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
outDir: "dist",
|
||||
rootDir: "src",
|
||||
},
|
||||
include: ["src"]
|
||||
})
|
||||
};
|
||||
const sharedFile: File = {
|
||||
path: `${solutionLocation}/shared/src/index.ts`,
|
||||
content: definition
|
||||
};
|
||||
const host = createServerHost([libFile, solution, libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]);
|
||||
const session = createSession(host);
|
||||
const service = session.getProjectService();
|
||||
service.openClientFile(apiFile.path);
|
||||
verifyApiProjectLoadAndSolutionPending();
|
||||
return { session, verifySolutionTreeLoaded, verifyApiAndSharedProjectLoadAndSolutionPending, apiFile, appFile, sharedFile };
|
||||
|
||||
function checkApiProject() {
|
||||
const apiProject = service.configuredProjects.get(apiConfig.path)!;
|
||||
checkProjectActualFiles(apiProject, [libFile.path, apiConfig.path, apiFile.path, sharedFile.path]);
|
||||
}
|
||||
function checkAppProject() {
|
||||
const appProject = service.configuredProjects.get(appConfig.path)!;
|
||||
checkProjectActualFiles(appProject, [libFile.path, appConfig.path, appFile.path, sharedFile.path]);
|
||||
}
|
||||
function checkSharedProject() {
|
||||
const sharedProject = service.configuredProjects.get(sharedConfig.path)!;
|
||||
checkProjectActualFiles(sharedProject, [libFile.path, sharedConfig.path, sharedFile.path]);
|
||||
}
|
||||
function checkSolutionLoadPending() {
|
||||
const solutionProject = service.configuredProjects.get(solution.path)!;
|
||||
assert.isFalse(solutionProject.isInitialLoadPending());
|
||||
}
|
||||
function checkSolutionLoadComplete() {
|
||||
const solutionProject = service.configuredProjects.get(solution.path)!;
|
||||
assert.isTrue(solutionProject.isInitialLoadPending());
|
||||
}
|
||||
function verifySolutionTreeLoaded() {
|
||||
checkNumberOfProjects(service, { configuredProjects: 4 });
|
||||
checkApiProject();
|
||||
checkAppProject();
|
||||
checkSharedProject();
|
||||
checkSolutionLoadPending();
|
||||
}
|
||||
|
||||
function verifyApiProjectLoadAndSolutionPending() {
|
||||
checkNumberOfProjects(service, { configuredProjects: 2 });
|
||||
checkApiProject();
|
||||
checkSolutionLoadComplete();
|
||||
}
|
||||
|
||||
function verifyApiAndSharedProjectLoadAndSolutionPending() {
|
||||
checkNumberOfProjects(service, { configuredProjects: 3 });
|
||||
checkApiProject();
|
||||
checkSharedProject();
|
||||
checkSolutionLoadComplete();
|
||||
}
|
||||
}
|
||||
|
||||
it("when using arrow function assignment", () => {
|
||||
const { session, apiFile, appFile, sharedFile, verifySolutionTreeLoaded } = setup(
|
||||
`export const dog = () => { };`,
|
||||
`shared.dog();`
|
||||
);
|
||||
|
||||
// Find all references
|
||||
const response = session.executeCommandSeq<protocol.ReferencesRequest>({
|
||||
command: protocol.CommandTypes.References,
|
||||
arguments: protocolFileLocationFromSubstring(apiFile, "dog")
|
||||
}).response as protocol.ReferencesResponseBody;
|
||||
assert.deepEqual(response, {
|
||||
refs: [
|
||||
makeReferenceItem({
|
||||
file: sharedFile,
|
||||
text: "dog",
|
||||
contextText: sharedFile.content,
|
||||
isDefinition: true,
|
||||
lineText: sharedFile.content,
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: apiFile,
|
||||
text: "dog",
|
||||
isDefinition: false,
|
||||
lineText: "shared.dog();",
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: appFile,
|
||||
text: "dog",
|
||||
isDefinition: false,
|
||||
lineText: "shared.dog();",
|
||||
})
|
||||
],
|
||||
symbolName: "dog",
|
||||
symbolStartOffset: protocolLocationFromSubstring(apiFile.content, "dog").offset,
|
||||
symbolDisplayString: "const dog: () => void"
|
||||
});
|
||||
verifySolutionTreeLoaded();
|
||||
});
|
||||
|
||||
it("when using arrow function as object literal property", () => {
|
||||
const { session, apiFile, appFile, sharedFile, verifySolutionTreeLoaded } = setup(
|
||||
`export const foo = { bar: () => { } };`,
|
||||
`shared.foo.bar();`
|
||||
);
|
||||
|
||||
// Find all references
|
||||
const response = session.executeCommandSeq<protocol.ReferencesRequest>({
|
||||
command: protocol.CommandTypes.References,
|
||||
arguments: protocolFileLocationFromSubstring(apiFile, "bar")
|
||||
}).response as protocol.ReferencesResponseBody;
|
||||
assert.deepEqual(response, {
|
||||
refs: [
|
||||
makeReferenceItem({
|
||||
file: sharedFile,
|
||||
text: "bar",
|
||||
contextText: `bar: () => { }`,
|
||||
isDefinition: true,
|
||||
lineText: sharedFile.content,
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: apiFile,
|
||||
text: "bar",
|
||||
isDefinition: false,
|
||||
lineText: "shared.foo.bar();",
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: appFile,
|
||||
text: "bar",
|
||||
isDefinition: false,
|
||||
lineText: "shared.foo.bar();",
|
||||
})
|
||||
],
|
||||
symbolName: "bar",
|
||||
symbolStartOffset: protocolLocationFromSubstring(apiFile.content, "bar").offset,
|
||||
symbolDisplayString: "(property) bar: () => void"
|
||||
});
|
||||
verifySolutionTreeLoaded();
|
||||
});
|
||||
|
||||
it("when using object literal property", () => {
|
||||
const { session, apiFile, appFile, sharedFile, verifySolutionTreeLoaded } = setup(
|
||||
`export const foo = { baz: "BAZ" };`,
|
||||
`shared.foo.baz;`
|
||||
);
|
||||
|
||||
// Find all references
|
||||
const response = session.executeCommandSeq<protocol.ReferencesRequest>({
|
||||
command: protocol.CommandTypes.References,
|
||||
arguments: protocolFileLocationFromSubstring(apiFile, "baz")
|
||||
}).response as protocol.ReferencesResponseBody;
|
||||
assert.deepEqual(response, {
|
||||
refs: [
|
||||
makeReferenceItem({
|
||||
file: sharedFile,
|
||||
text: "baz",
|
||||
contextText: `baz: "BAZ"`,
|
||||
isDefinition: true,
|
||||
lineText: sharedFile.content,
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: apiFile,
|
||||
text: "baz",
|
||||
isDefinition: false,
|
||||
lineText: "shared.foo.baz;",
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: appFile,
|
||||
text: "baz",
|
||||
isDefinition: false,
|
||||
lineText: "shared.foo.baz;",
|
||||
})
|
||||
],
|
||||
symbolName: "baz",
|
||||
symbolStartOffset: protocolLocationFromSubstring(apiFile.content, "baz").offset,
|
||||
symbolDisplayString: `(property) baz: string`
|
||||
});
|
||||
verifySolutionTreeLoaded();
|
||||
});
|
||||
|
||||
it("when using method of class expression", () => {
|
||||
const { session, apiFile, appFile, sharedFile, verifySolutionTreeLoaded } = setup(
|
||||
`export const foo = class { fly() {} };`,
|
||||
`const instance = new shared.foo();
|
||||
instance.fly();`
|
||||
);
|
||||
|
||||
// Find all references
|
||||
const response = session.executeCommandSeq<protocol.ReferencesRequest>({
|
||||
command: protocol.CommandTypes.References,
|
||||
arguments: protocolFileLocationFromSubstring(apiFile, "fly")
|
||||
}).response as protocol.ReferencesResponseBody;
|
||||
assert.deepEqual(response, {
|
||||
refs: [
|
||||
makeReferenceItem({
|
||||
file: sharedFile,
|
||||
text: "fly",
|
||||
contextText: `fly() {}`,
|
||||
isDefinition: true,
|
||||
lineText: sharedFile.content,
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: apiFile,
|
||||
text: "fly",
|
||||
isDefinition: false,
|
||||
lineText: "instance.fly();",
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: appFile,
|
||||
text: "fly",
|
||||
isDefinition: false,
|
||||
lineText: "instance.fly();",
|
||||
})
|
||||
],
|
||||
symbolName: "fly",
|
||||
symbolStartOffset: protocolLocationFromSubstring(apiFile.content, "fly").offset,
|
||||
symbolDisplayString: `(method) foo.fly(): void`
|
||||
});
|
||||
verifySolutionTreeLoaded();
|
||||
});
|
||||
|
||||
it("when using arrow function as object literal property is loaded through indirect assignment with original declaration local to project is treated as local", () => {
|
||||
const { session, apiFile, sharedFile, verifyApiAndSharedProjectLoadAndSolutionPending } = setup(
|
||||
`const local = { bar: () => { } };
|
||||
export const foo = local;`,
|
||||
`shared.foo.bar();`
|
||||
);
|
||||
|
||||
// Find all references
|
||||
const response = session.executeCommandSeq<protocol.ReferencesRequest>({
|
||||
command: protocol.CommandTypes.References,
|
||||
arguments: protocolFileLocationFromSubstring(apiFile, "bar")
|
||||
}).response as protocol.ReferencesResponseBody;
|
||||
assert.deepEqual(response, {
|
||||
refs: [
|
||||
makeReferenceItem({
|
||||
file: sharedFile,
|
||||
text: "bar",
|
||||
contextText: `bar: () => { }`,
|
||||
isDefinition: true,
|
||||
lineText: `const local = { bar: () => { } };`,
|
||||
}),
|
||||
makeReferenceItem({
|
||||
file: apiFile,
|
||||
text: "bar",
|
||||
isDefinition: false,
|
||||
lineText: "shared.foo.bar();",
|
||||
}),
|
||||
],
|
||||
symbolName: "bar",
|
||||
symbolStartOffset: protocolLocationFromSubstring(apiFile.content, "bar").offset,
|
||||
symbolDisplayString: "(property) bar: () => void"
|
||||
});
|
||||
verifyApiAndSharedProjectLoadAndSolutionPending();
|
||||
});
|
||||
});
|
||||
|
||||
it("when disableSolutionSearching is true, solution and siblings are not loaded", () => {
|
||||
const solutionLocation = "/user/username/projects/solution";
|
||||
const solution: File = {
|
||||
|
||||
@@ -4,7 +4,7 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(6,5): error TS2502: 'c'
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(9,5): error TS2502: 'd' is referenced directly or indirectly in its own type annotation.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(14,10): error TS7023: 'g' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(17,5): error TS7023: 'f1' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(22,10): error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(22,5): error TS7023: 'f2' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(25,10): error TS7023: 'h' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(27,14): error TS7023: 'foo' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
tests/cases/compiler/implicitAnyFromCircularInference.ts(45,9): error TS7023: 'x' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
@@ -45,8 +45,8 @@ tests/cases/compiler/implicitAnyFromCircularInference.ts(45,9): error TS7023: 'x
|
||||
|
||||
// Error expected
|
||||
var f2 = () => f2();
|
||||
~~~~~~~~~~
|
||||
!!! error TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
~~
|
||||
!!! error TS7023: 'f2' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
|
||||
|
||||
// Error expected
|
||||
function h() {
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
// @allowJs: true
|
||||
// @Filename: foo.js
|
||||
////x.test = /*def*/() => { }
|
||||
////x./*def*/test = () => { }
|
||||
////x.[|/*ref*/test|]();
|
||||
////x./*defFn*/test3 = function () { }
|
||||
////x.[|/*refFn*/test3|]();
|
||||
|
||||
verify.goToDefinition("ref", "def");
|
||||
verify.goToDefinition("refFn", "defFn");
|
||||
|
||||
@@ -11,14 +11,18 @@
|
||||
////[|/*useG*/g|]();
|
||||
////[|/*useH*/h|]();
|
||||
|
||||
////const i = /*i*/() => 0;
|
||||
////const /*i*/i = () => 0;
|
||||
////const /*iFn*/iFn = function () { return 0; };
|
||||
////const /*j*/j = i;
|
||||
|
||||
////[|/*useI*/i|]();
|
||||
////[|/*useIFn*/iFn|]();
|
||||
////[|/*useJ*/j|]();
|
||||
|
||||
////const o = { m: /*m*/() => 0 };
|
||||
////const o = { /*m*/m: () => 0 };
|
||||
////o.[|/*useM*/m|]();
|
||||
////const oFn = { /*mFn*/mFn: function () { return 0; } };
|
||||
////oFn.[|/*useMFn*/mFn|]();
|
||||
|
||||
////class Component { /*componentCtr*/constructor(props: {}) {} }
|
||||
////type ComponentClass = /*ComponentClass*/new () => Component;
|
||||
@@ -44,8 +48,10 @@ verify.goToDefinition({
|
||||
useH: ["h", "f"],
|
||||
|
||||
useI: "i",
|
||||
useIFn: "iFn",
|
||||
useJ: ["j", "i"],
|
||||
useM: "m",
|
||||
useMFn: "mFn",
|
||||
|
||||
jsxMyComponent: "MyComponent",
|
||||
newMyComponent: ["MyComponent", "componentCtr"],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
////
|
||||
////const f6 = (i: I, j: J, b: boolean) => b ? i : j;
|
||||
////
|
||||
////const f7 = /*f7Def*/(i: I) => {};
|
||||
////const /*f7Def*/f7 = (i: I) => {};
|
||||
////
|
||||
////function f8(i: I): I;
|
||||
////function f8(j: J): J;
|
||||
|
||||
Reference in New Issue
Block a user