Skip to content

Commit

Permalink
Find Source Definition (microsoft#48264)
Browse files Browse the repository at this point in the history
* Prototype resolving to JS when go-to-def aliases all resolve to ambient declarations

* Add test infrastructure

* Start fleshing out test coverage

* Fix some go-to-def stuff

* Finish lodash test case

* Make go-to-implementation never return ambient results

* Build new functionality into go-to-implementation

* Update baselines

* Two more test cases

* Refine definition searches for unresolved imports

* Revert "Build new functionality into go-to-implementation"

This reverts commit 381799d.

* Fix tests

* Revert go-to-implementation changes

* Wow a bunch of code was unnecessary

* Update baselines and go-to-def test

* Fix navigation on symbols that are not aliases but resolve through aliases in chain

* Temporarily replace go-to-def with new command implementation

* Revert "Temporarily replace go-to-def with new command implementation"

This reverts commit 34c6cfd.

* Revert "Wow a bunch of code was unnecessary"

This reverts commit 1cb2ba6.

* Bring back some deleted code needed for a new test case

* Clean up a little

* Rename more stuff

* Update test

* Update API baseline

* Temporarily replace go-to-def with new command implementation

* PR review fixes

* Fix getTopMostDeclarationNamesInFile

* Rename local

* Use hash set

* Remove option from commandLineParser

* Keep noDtsResolution project around

* Handle AuxiliaryProject kind in ScriptInfo getDefaultProject etc.

* Do not run updateGraph in the background for AuxiliaryProject

* Don’t create auxiliary project outside of semantic mode

* No-op on scheduled invalidation

* Add comments to unit test

* Sync compiler options to auxiliary project

* Fix case sensitivity

* Update extensionIsOk with new file extensions

* PR feedback

* Update API baseline

* Mark scheduleInvalidateResolutionsOfFailedLookupLocations internal

* Use same heuristics on property accesses of loosely-resolvable aliases as unresolvable named imports

* Rename command, and no need to return the bound span

* Update API baseline
  • Loading branch information
andrewbranch authored and Jack-Works committed Apr 22, 2022
1 parent df4dc24 commit 890a318
Show file tree
Hide file tree
Showing 36 changed files with 1,056 additions and 104 deletions.
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9206,7 +9206,7 @@ namespace ts {
}
}
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217
type = getUnionType(sourceTypes!, UnionReduction.Subtype);
type = getUnionType(sourceTypes!);
}
}
const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor));
Expand Down
43 changes: 33 additions & 10 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ namespace ts {
JavaScript, /** '.js' or '.jsx' */
Json, /** '.json' */
TSConfig, /** '.json' with `tsconfig` used instead of `index` */
DtsOnly /** Only '.d.ts' */
DtsOnly, /** Only '.d.ts' */
TsOnly, /** '.[cm]tsx?' but not .d.ts variants */
}

interface PathAndPackageId {
Expand Down Expand Up @@ -1290,7 +1291,19 @@ namespace ts {
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
/* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations {
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference);
let extensions;
if (lookupConfig) {
extensions = tsconfigExtensions;
}
else if (compilerOptions.noDtsResolution) {
extensions = [Extensions.TsOnly];
if (compilerOptions.allowJs) extensions.push(Extensions.JavaScript);
if (compilerOptions.resolveJsonModule) extensions.push(Extensions.Json);
}
else {
extensions = compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions;
}
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, redirectedReference);
}

function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations {
Expand All @@ -1299,14 +1312,19 @@ namespace ts {
const failedLookupLocations: string[] = [];
// conditions are only used by the node12/nodenext resolver - there's no priority order in the list,
//it's essentially a set (priority is determined by object insertion order in the object we look at).
const conditions = features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"];
if (compilerOptions.noDtsResolution) {
conditions.pop();
}

const state: ModuleResolutionState = {
compilerOptions,
host,
traceEnabled,
failedLookupLocations,
packageJsonInfoCache: cache,
features,
conditions: features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"]
conditions,
};

const result = forEach(extensions, ext => tryResolve(ext));
Expand Down Expand Up @@ -1533,20 +1551,22 @@ namespace ts {
default: return tryExtension(Extension.Dts);
}
case Extensions.TypeScript:
case Extensions.TsOnly:
const useDts = extensions === Extensions.TypeScript;
switch (originalExtension) {
case Extension.Mjs:
case Extension.Mts:
case Extension.Dmts:
return tryExtension(Extension.Mts) || tryExtension(Extension.Dmts);
return tryExtension(Extension.Mts) || (useDts ? tryExtension(Extension.Dmts) : undefined);
case Extension.Cjs:
case Extension.Cts:
case Extension.Dcts:
return tryExtension(Extension.Cts) || tryExtension(Extension.Dcts);
return tryExtension(Extension.Cts) || (useDts ? tryExtension(Extension.Dcts) : undefined);
case Extension.Json:
candidate += Extension.Json;
return tryExtension(Extension.Dts);
return useDts ? tryExtension(Extension.Dts) : undefined;
default:
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts);
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || (useDts ? tryExtension(Extension.Dts) : undefined);
}
case Extensions.JavaScript:
switch (originalExtension) {
Expand Down Expand Up @@ -1813,6 +1833,7 @@ namespace ts {
switch (extensions) {
case Extensions.JavaScript:
case Extensions.Json:
case Extensions.TsOnly:
packageFile = readPackageJsonMainField(jsonContent, candidate, state);
break;
case Extensions.TypeScript:
Expand Down Expand Up @@ -1893,14 +1914,16 @@ namespace ts {
function extensionIsOk(extensions: Extensions, extension: Extension): boolean {
switch (extensions) {
case Extensions.JavaScript:
return extension === Extension.Js || extension === Extension.Jsx;
return extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs;
case Extensions.TSConfig:
case Extensions.Json:
return extension === Extension.Json;
case Extensions.TypeScript:
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts;
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts || extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts;
case Extensions.TsOnly:
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts;
case Extensions.DtsOnly:
return extension === Extension.Dts;
return extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts;
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4668,8 +4668,10 @@ namespace ts {
export type AnyImportOrRequire = AnyImportSyntax | VariableDeclarationInitializedTo<RequireOrImportCall>;

/* @internal */
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;
export type AnyImportOrBareOrAccessedRequire = AnyImportSyntax | VariableDeclarationInitializedTo<RequireOrImportCall | AccessExpression>;

/* @internal */
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;

/* @internal */
export type AnyImportOrReExport = AnyImportSyntax | ExportDeclaration;
Expand Down Expand Up @@ -6178,6 +6180,8 @@ namespace ts {
assumeChangesOnlyAffectDirectDependencies?: boolean;
noLib?: boolean;
noResolve?: boolean;
/*@internal*/
noDtsResolution?: boolean;
noUncheckedIndexedAccess?: boolean;
out?: string;
outDir?: string;
Expand Down
81 changes: 70 additions & 11 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,10 @@ namespace ts {
}
}

export function isAnyImportOrBareOrAccessedRequire(node: Node): node is AnyImportOrBareOrAccessedRequire {
return isAnyImportSyntax(node) || isVariableDeclarationInitializedToBareOrAccessedRequire(node);
}

export function isLateVisibilityPaintedStatement(node: Node): node is LateVisibilityPaintedStatement {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
Expand Down Expand Up @@ -2558,14 +2562,14 @@ namespace ts {
return decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer);
}

export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrRequire): string | undefined {
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire): StringLiteralLike | undefined {
switch (node.kind) {
case SyntaxKind.VariableDeclaration:
return node.initializer.arguments[0].text;
return findAncestor(node.initializer, (node): node is RequireOrImportCall => isRequireCall(node, /*requireStringLiteralLikeArgument*/ true))?.arguments[0];
case SyntaxKind.ImportDeclaration:
return tryCast(node.moduleSpecifier, isStringLiteralLike)?.text;
return tryCast(node.moduleSpecifier, isStringLiteralLike);
case SyntaxKind.ImportEqualsDeclaration:
return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike)?.text;
return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike);
default:
Debug.assertNever(node);
}
Expand Down Expand Up @@ -3131,21 +3135,31 @@ namespace ts {
// export = <EntityNameExpression>
// export default <EntityNameExpression>
// module.exports = <EntityNameExpression>
// {<Identifier>}
// {name: <EntityNameExpression>}
// module.exports.x = <EntityNameExpression>
// const x = require("...")
// const { x } = require("...")
// const x = require("...").y
// const { x } = require("...").y
export function isAliasSymbolDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
if (node.kind === SyntaxKind.ImportEqualsDeclaration ||
node.kind === SyntaxKind.NamespaceExportDeclaration ||
node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name ||
node.kind === SyntaxKind.NamespaceImport ||
node.kind === SyntaxKind.NamespaceExport ||
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment)
) {
return true;
}

return isInJSFile(node) && (
isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) ||
isPropertyAccessExpression(node) && isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAliasableExpression(node.parent.right) ||
node.kind === SyntaxKind.ShorthandPropertyAssignment ||
node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer);
isPropertyAccessExpression(node)
&& isBinaryExpression(node.parent)
&& node.parent.left === node
&& node.parent.operatorToken.kind === SyntaxKind.EqualsToken
&& isAliasableExpression(node.parent.right));
}

export function getAliasDeclarationFromName(node: EntityName): Declaration | undefined {
Expand All @@ -3156,6 +3170,7 @@ namespace ts {
case SyntaxKind.ExportSpecifier:
case SyntaxKind.ExportAssignment:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.NamespaceExport:
return node.parent as Declaration;
case SyntaxKind.QualifiedName:
do {
Expand Down Expand Up @@ -5142,6 +5157,11 @@ namespace ts {
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).name === node);
}

export function isRightSideOfAccessExpression(node: Node) {
return isPropertyAccessExpression(node.parent) && node.parent.name === node
|| isElementAccessExpression(node.parent) && node.parent.argumentExpression === node;
}

export function isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(node: Node) {
return isQualifiedName(node.parent) && node.parent.right === node
|| isPropertyAccessExpression(node.parent) && node.parent.name === node
Expand Down Expand Up @@ -5829,6 +5849,45 @@ namespace ts {
return expr;
}

export function forEachNameInAccessChainWalkingLeft<T>(name: MemberName | StringLiteralLike, action: (name: MemberName | StringLiteralLike) => T | undefined): T | undefined {
if (isAccessExpression(name.parent) && isRightSideOfAccessExpression(name)) {
return walkAccessExpression(name.parent);
}

function walkAccessExpression(access: AccessExpression): T | undefined {
if (access.kind === SyntaxKind.PropertyAccessExpression) {
const res = action(access.name);
if (res !== undefined) {
return res;
}
}
else if (access.kind === SyntaxKind.ElementAccessExpression) {
if (isIdentifier(access.argumentExpression) || isStringLiteralLike(access.argumentExpression)) {
const res = action(access.argumentExpression);
if (res !== undefined) {
return res;
}
}
else {
// Chain interrupted by non-static-name access 'x[expr()].y.z'
return undefined;
}
}

if (isAccessExpression(access.expression)) {
return walkAccessExpression(access.expression);
}
if (isIdentifier(access.expression)) {
// End of chain at Identifier 'x.y.z'
return action(access.expression);
}
// End of chain at non-Identifier 'x().y.z'
return undefined;
}
}



export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
while (true) {
switch (node.kind) {
Expand Down
19 changes: 18 additions & 1 deletion src/harness/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ namespace ts.server {
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan {
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);

const request = this.processRequest<protocol.DefinitionRequest>(CommandNames.DefinitionAndBoundSpan, args);
const request = this.processRequest<protocol.DefinitionAndBoundSpanRequest>(CommandNames.DefinitionAndBoundSpan, args);
const response = this.processResponse<protocol.DefinitionInfoAndBoundSpanResponse>(request);
const body = Debug.checkDefined(response.body); // TODO: GH#18217

Expand Down Expand Up @@ -332,6 +332,23 @@ namespace ts.server {
}));
}

getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfo[] {
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.FindSourceDefinitionRequest>(CommandNames.FindSourceDefinition, args);
const response = this.processResponse<protocol.DefinitionResponse>(request);
const body = Debug.checkDefined(response.body); // TODO: GH#18217

return body.map(entry => ({
containerKind: ScriptElementKind.unknown,
containerName: "",
fileName: entry.file,
textSpan: this.decodeSpan(entry),
kind: ScriptElementKind.unknown,
name: "",
unverified: entry.unverified,
}));
}

getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
const args = this.createFileLocationRequestArgs(fileName, position);

Expand Down
Loading

0 comments on commit 890a318

Please sign in to comment.