* Writes to module global `global` from OTHER files. A production write * ` . = Cls(...)` adds a type; any other production write * (another value, a tuple target, `setattr`) makes the type unknown. Test * files install doubles (`settings.conn = MagicMock()`, `monkeypatch.setattr`)
(global: Node, context: ResolutionContext)
| 771 | * a ref inside a writer resolves nothing through the global. |
| 772 | */ |
| 773 | function pythonExternalWrites(global: Node, context: ResolutionContext): { writes: Array<{ type: string; file: string }>; unknown: boolean; writers: Set<string> } { |
| 774 | return pythonNameScan(context, `external\0${global.id}`, () => { |
| 775 | const out = { writes: [] as Array<{ type: string; file: string }>, unknown: false, writers: new Set<string>() }; |
| 776 | const name = global.name; |
| 777 | const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| 778 | for (const file of context.getAllFiles()) { |
| 779 | if (file === global.filePath || !/\.pyi?$/.test(file) || !context.readFile(file)?.includes(name)) continue; |
| 780 | const test = isPythonTestFile(file); |
| 781 | const { aliases, ambiguous } = pythonModuleAliases(file, global.filePath, context); |
| 782 | if (!test && !aliases.length && !ambiguous.length) continue; |
| 783 | const spell = (names: string[]) => `(?:${names.map(escape).join('|')})`; |
| 784 | // Test files: any receiver (the double may sit on any spelling of the module). |
| 785 | const receiver = test ? '[\\w.]+' : spell([...aliases, ...ambiguous]); |
| 786 | const target = new RegExp(`(?<![\\w.])${receiver}\\.${name}\\b(?!\\s*[.\\[(])`); |
| 787 | const exact = new RegExp(`^${test ? receiver : spell(aliases.length ? aliases : ['\\0'])}\\.${name}(?:\\s*:[^=]*)?$`); |
| 788 | const dynamic = new RegExp(`\\b(?:setattr|patch\\.object)\\s*\\(\\s*${receiver}\\s*,\\s*['"]${name}['"]` + |
| 789 | `|(?<![\\w.])${receiver}\\.__dict__\\s*(?:\\[|\\.\\s*update\\s*\\()`); |
| 790 | const lines = pythonMemberLines(file, context); |
| 791 | const statements = pythonStatementStarts(file, context); |
| 792 | const script = pythonMainBlockLines(file, context); |
| 793 | for (let i = 0; i < lines.length; i++) { |
| 794 | if (!statements[i] || script.has(i)) continue; |
| 795 | let statement = lines[i]!; |
| 796 | for (let j = i + 1; j < lines.length && !statements[j]; j++) statement += '\n' + lines[j]; |
| 797 | if (!statement.includes(name)) continue; |
| 798 | const assignment = pythonAssignment(statement); |
| 799 | const assigned = assignment?.targets.some(t => target.test(t.trim())) ?? false; |
| 800 | if (!assigned && !dynamic.test(statement)) continue; |
| 801 | if (test) { out.writers.add(file); continue; } |
| 802 | // A write through an ambiguous spelling may land on another module: unknown. |
| 803 | const single = assignment && assignment.targets.length === 1 && !assignment.augmented && exact.test(assignment.targets[0]!.trim()); |
| 804 | const value = single ? assignment!.value.trim() : ''; |
| 805 | if (value === 'None') continue; |
| 806 | const constructor = value ? pythonConstructorCall(value) : null; |
| 807 | if (!constructor) { out.unknown = true; return out; } |
| 808 | out.writes.push({ type: constructor, file }); |
| 809 | } |
| 810 | } |
| 811 | return out; |
| 812 | }); |
| 813 | } |
| 814 | |
| 815 | /** |
| 816 | * Whether the global's own module can write it through its namespace dict: |
no test coverage detected