Fixed regression in hover and definition provider relating to aliased symbols in "from import" statements. Significantly reduced complexity of definition provider by using existing code in other modules.

This commit is contained in:
Eric Traut 2019-10-20 16:25:48 -07:00
parent 25dbc4250d
commit 68ca16c1b0
2 changed files with 23 additions and 132 deletions

View File

@ -23,6 +23,15 @@ export function getDeclarationsForNameNode(node: NameNode): Declaration[] | unde
let declarations: Declaration[] | undefined;
const nameValue = node.nameToken.value;
// If the node is part of a "from X import Y as Z" statement and the node
// is the "Y" (non-aliased) name, don't return any declarations for it
// because this name isn't in the symbol table.
if (node.parent && node.parent.nodeType === ParseNodeType.ImportFromAs &&
node.parent.alias && node === node.parent.name) {
return undefined;
}
if (node.parent && node.parent.nodeType === ParseNodeType.MemberAccess &&
node === node.parent.memberName) {

View File

@ -11,23 +11,13 @@
*/
import { ImportLookup } from '../analyzer/analyzerFileInfo';
import * as AnalyzerNodeInfo from '../analyzer/analyzerNodeInfo';
import { Declaration } from '../analyzer/declaration';
import { resolveAliasDeclaration } from '../analyzer/declarationUtils';
import { getDeclarationsForNameNode, resolveAliasDeclaration } from '../analyzer/declarationUtils';
import * as ParseTreeUtils from '../analyzer/parseTreeUtils';
import { Symbol } from '../analyzer/symbol';
import { ModuleType, TypeCategory } from '../analyzer/types';
import * as TypeUtils from '../analyzer/typeUtils';
import { DiagnosticTextPosition, DiagnosticTextRange, DocumentTextRange } from '../common/diagnostic';
import { isFile } from '../common/pathUtils';
import { DiagnosticTextPosition, DocumentTextRange } from '../common/diagnostic';
import { convertPositionToOffset } from '../common/positionUtils';
import { TextRange } from '../common/textRange';
import { MemberAccessExpressionNode, ModuleNameNode, NameNode, ParseNodeType } from '../parser/parseNodes';
import { ParseNodeType } from '../parser/parseNodes';
import { ParseResults } from '../parser/parser';
const _startOfFilePosition: DiagnosticTextPosition = { line: 0, column: 0 };
const _startOfFileRange: DiagnosticTextRange = { start: _startOfFilePosition, end: _startOfFilePosition };
export class DefinitionProvider {
static getDefinitionsForPosition(parseResults: ParseResults,
position: DiagnosticTextPosition, importLookup: ImportLookup):
@ -46,128 +36,20 @@ export class DefinitionProvider {
const definitions: DocumentTextRange[] = [];
if (node.nodeType === ParseNodeType.Name) {
// Is the user hovering over a member name? If so, we need to search
// in the scope of that type rather than the current node's scope.
if (node.parent && node.parent.nodeType === ParseNodeType.MemberAccess &&
node === node.parent.memberName) {
this._addDefinitionsForMemberAccessNode(definitions, node.parent, importLookup);
} else if (node.parent && node.parent.nodeType === ParseNodeType.ModuleName) {
this._addDefinitionsForModuleNameNode(definitions, node.parent, offset);
} else {
this._addDefinitionsForNameNode(definitions, node, importLookup);
const declarations = getDeclarationsForNameNode(node);
if (declarations) {
declarations.forEach(decl => {
const resolvedDecl = resolveAliasDeclaration(decl, importLookup);
if (resolvedDecl && resolvedDecl.path) {
definitions.push({
path: resolvedDecl.path,
range: resolvedDecl.range
});
}
});
}
}
return definitions.length > 0 ? definitions : undefined;
}
private static _addDefinitionsForMemberAccessNode(definitions: DocumentTextRange[],
node: MemberAccessExpressionNode, importLookup: ImportLookup) {
const baseType = AnalyzerNodeInfo.getExpressionType(node.leftExpression);
if (!baseType) {
return;
}
const memberName = node.memberName.nameToken.value;
TypeUtils.doForSubtypes(baseType, subtype => {
let symbol: Symbol | undefined;
if (subtype.category === TypeCategory.Class) {
const member = TypeUtils.lookUpClassMember(subtype, memberName);
if (member) {
symbol = member.symbol;
}
} else if (subtype.category === TypeCategory.Object) {
const member = TypeUtils.lookUpObjectMember(subtype, memberName);
if (member) {
symbol = member.symbol;
}
} else if (subtype.category === TypeCategory.Module) {
symbol = ModuleType.getField(subtype, memberName);
}
if (symbol) {
const declarations = symbol.getDeclarations();
this._addResultsForDeclarations(definitions, declarations, importLookup);
}
return subtype;
});
}
private static _addDefinitionsForNameNode(definitions: DocumentTextRange[],
node: NameNode, importLookup: ImportLookup) {
const scopeNode = ParseTreeUtils.getScopeNodeForNode(node);
if (!scopeNode) {
return;
}
const scope = AnalyzerNodeInfo.getScopeRecursive(scopeNode);
if (!scope) {
return;
}
const symbolWithScope = scope.lookUpSymbolRecursive(node.nameToken.value);
if (!symbolWithScope) {
return;
}
const declarations = symbolWithScope.symbol.getDeclarations();
if (declarations) {
this._addResultsForDeclarations(definitions, declarations, importLookup);
}
}
private static _addResultsForDeclarations(definitions: DocumentTextRange[],
declarations: Declaration[], importLookup: ImportLookup) {
declarations.forEach(decl => {
const resolvedDecl = resolveAliasDeclaration(decl, importLookup);
if (resolvedDecl && resolvedDecl.path) {
definitions.push({
path: resolvedDecl.path,
range: resolvedDecl.range
});
}
});
}
private static _addDefinitionsForModuleNameNode(definitions: DocumentTextRange[],
node: ModuleNameNode, offset: number) {
// If this is an imported module name, try to map the position
// to the resolved import path.
const importInfo = AnalyzerNodeInfo.getImportInfo(node);
if (!importInfo) {
return;
}
const pathOffset = node.nameParts.findIndex(range => {
return offset >= range.start && offset < TextRange.getEnd(range);
});
if (pathOffset < 0) {
return;
}
// Handle imports that were resolved partially.
if (pathOffset >= importInfo.resolvedPaths.length) {
return;
}
// If it's a directory, don't return it. The caller expects
// the path to point to files only.
const path = importInfo.resolvedPaths[pathOffset];
if (!isFile(path)) {
return;
}
definitions.push({
path,
range: _startOfFileRange
});
}
}