* Whether `name`, read at the ref, is bound by the calling function or one that * encloses it (parameter, assignment, loop, `as`, lambda, import) rather than * being the module global. With `importsBind` false, an import of the name is * not a shadow: it binds the same module the file imports.
(name: string, ref: UnresolvedRef, context: ResolutionContext, importsBind: boolean)
| 581 | * not a shadow: it binds the same module the file imports. |
| 582 | */ |
| 583 | function pythonBindsLocally(name: string, ref: UnresolvedRef, context: ResolutionContext, importsBind: boolean): boolean { |
| 584 | const lines = pythonMemberLines(ref.filePath, context); |
| 585 | const statements = pythonStatementStarts(ref.filePath, context); |
| 586 | const param = new RegExp(`[(,]\\s*\\*{0,2}${name}\\s*[:=,)]`); |
| 587 | const scopes = context.getNodesInFile(ref.filePath).filter(n => |
| 588 | (n.kind === 'function' || n.kind === 'method') && n.startLine <= ref.line && n.endLine >= ref.line) |
| 589 | .sort((a, b) => b.startLine - a.startLine); |
| 590 | for (const scope of scopes) { |
| 591 | const own = pythonOwnLines(scope, ref.filePath, context); |
| 592 | const bindings = own.flatMap(i => pythonLineBindings(lines, i, statements, name)); |
| 593 | if (bindings.some(b => b.kind === 'global')) return false; |
| 594 | const def = own.find(i => /^\s*(?:async\s+)?def\b/.test(lines[i]!)); |
| 595 | let header = scope.signature ?? ''; |
| 596 | for (let j = def ?? lines.length; j < lines.length && (j === def || !statements[j]); j++) header += lines[j]; |
| 597 | if (param.test(header)) return true; |
| 598 | if (bindings.some(b => b.kind !== 'global' && (b.kind !== 'import' || importsBind))) return true; |
| 599 | } |
| 600 | return false; |
| 601 | } |
| 602 | |
| 603 | /** Distinct sources a file imports `name` from, at any scope (`from a import x` → `a:x`). */ |
| 604 | function pythonImportKeys(name: string, filePath: string, context: ResolutionContext): Set<string> { |
no test coverage detected