@zzzen/pyright-internal 1.2.0-dev.20230423 → 1.2.0-dev.20230507

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/dist/analyzer/analyzerFileInfo.d.ts +4 -1
  2. package/dist/analyzer/analyzerFileInfo.js.map +1 -1
  3. package/dist/analyzer/backgroundAnalysisProgram.d.ts +7 -4
  4. package/dist/analyzer/backgroundAnalysisProgram.js +22 -7
  5. package/dist/analyzer/backgroundAnalysisProgram.js.map +1 -1
  6. package/dist/analyzer/binder.js +12 -7
  7. package/dist/analyzer/binder.js.map +1 -1
  8. package/dist/analyzer/checker.d.ts +1 -0
  9. package/dist/analyzer/checker.js +58 -7
  10. package/dist/analyzer/checker.js.map +1 -1
  11. package/dist/analyzer/constructors.d.ts +6 -0
  12. package/dist/analyzer/constructors.js +456 -0
  13. package/dist/analyzer/constructors.js.map +1 -0
  14. package/dist/analyzer/dataClasses.js +89 -2
  15. package/dist/analyzer/dataClasses.js.map +1 -1
  16. package/dist/analyzer/declarationUtils.d.ts +6 -1
  17. package/dist/analyzer/declarationUtils.js +10 -8
  18. package/dist/analyzer/declarationUtils.js.map +1 -1
  19. package/dist/analyzer/enums.js +54 -8
  20. package/dist/analyzer/enums.js.map +1 -1
  21. package/dist/analyzer/importResolver.d.ts +15 -15
  22. package/dist/analyzer/importResolver.js +477 -477
  23. package/dist/analyzer/importResolver.js.map +1 -1
  24. package/dist/analyzer/importStatementUtils.d.ts +2 -2
  25. package/dist/analyzer/importStatementUtils.js.map +1 -1
  26. package/dist/analyzer/namedTuples.d.ts +1 -1
  27. package/dist/analyzer/namedTuples.js +30 -42
  28. package/dist/analyzer/namedTuples.js.map +1 -1
  29. package/dist/analyzer/operations.d.ts +16 -0
  30. package/dist/analyzer/operations.js +749 -0
  31. package/dist/analyzer/operations.js.map +1 -0
  32. package/dist/analyzer/packageTypeReport.d.ts +2 -1
  33. package/dist/analyzer/packageTypeReport.js +2 -1
  34. package/dist/analyzer/packageTypeReport.js.map +1 -1
  35. package/dist/analyzer/packageTypeVerifier.d.ts +1 -1
  36. package/dist/analyzer/packageTypeVerifier.js +27 -13
  37. package/dist/analyzer/packageTypeVerifier.js.map +1 -1
  38. package/dist/analyzer/parseTreeUtils.d.ts +4 -2
  39. package/dist/analyzer/parseTreeUtils.js +32 -1
  40. package/dist/analyzer/parseTreeUtils.js.map +1 -1
  41. package/dist/analyzer/patternMatching.js +16 -0
  42. package/dist/analyzer/patternMatching.js.map +1 -1
  43. package/dist/analyzer/program.d.ts +29 -53
  44. package/dist/analyzer/program.js +1050 -1649
  45. package/dist/analyzer/program.js.map +1 -1
  46. package/dist/analyzer/protocols.js +1 -1
  47. package/dist/analyzer/protocols.js.map +1 -1
  48. package/dist/analyzer/service.d.ts +14 -35
  49. package/dist/analyzer/service.js +92 -121
  50. package/dist/analyzer/service.js.map +1 -1
  51. package/dist/analyzer/sourceFile.d.ts +14 -49
  52. package/dist/analyzer/sourceFile.js +271 -291
  53. package/dist/analyzer/sourceFile.js.map +1 -1
  54. package/dist/analyzer/sourceFileInfoUtils.d.ts +2 -2
  55. package/dist/analyzer/sourceFileInfoUtils.js.map +1 -1
  56. package/dist/analyzer/typeEvaluator.js +417 -1570
  57. package/dist/analyzer/typeEvaluator.js.map +1 -1
  58. package/dist/analyzer/typeEvaluatorTypes.d.ts +44 -9
  59. package/dist/analyzer/typeEvaluatorTypes.js +33 -1
  60. package/dist/analyzer/typeEvaluatorTypes.js.map +1 -1
  61. package/dist/analyzer/typeGuards.js +5 -9
  62. package/dist/analyzer/typeGuards.js.map +1 -1
  63. package/dist/analyzer/typeStubWriter.js.map +1 -1
  64. package/dist/analyzer/typeUtils.d.ts +3 -0
  65. package/dist/analyzer/typeUtils.js +66 -9
  66. package/dist/analyzer/typeUtils.js.map +1 -1
  67. package/dist/analyzer/typeVarContext.d.ts +5 -5
  68. package/dist/analyzer/typeVarContext.js +7 -0
  69. package/dist/analyzer/typeVarContext.js.map +1 -1
  70. package/dist/analyzer/typedDicts.js +2 -2
  71. package/dist/analyzer/typedDicts.js.map +1 -1
  72. package/dist/analyzer/types.d.ts +4 -2
  73. package/dist/analyzer/types.js +7 -0
  74. package/dist/analyzer/types.js.map +1 -1
  75. package/dist/backgroundAnalysisBase.d.ts +10 -11
  76. package/dist/backgroundAnalysisBase.js +87 -87
  77. package/dist/backgroundAnalysisBase.js.map +1 -1
  78. package/dist/commands/dumpFileDebugInfoCommand.js +8 -8
  79. package/dist/commands/dumpFileDebugInfoCommand.js.map +1 -1
  80. package/dist/commands/quickActionCommand.js +4 -1
  81. package/dist/commands/quickActionCommand.js.map +1 -1
  82. package/dist/common/cancellationUtils.d.ts +1 -1
  83. package/dist/common/cancellationUtils.js +9 -9
  84. package/dist/common/cancellationUtils.js.map +1 -1
  85. package/dist/common/commandLineOptions.d.ts +1 -2
  86. package/dist/common/commandLineOptions.js.map +1 -1
  87. package/dist/common/configOptions.d.ts +2 -2
  88. package/dist/common/configOptions.js.map +1 -1
  89. package/dist/common/console.d.ts +5 -9
  90. package/dist/common/console.js +46 -33
  91. package/dist/common/console.js.map +1 -1
  92. package/dist/common/deferred.js +10 -10
  93. package/dist/common/deferred.js.map +1 -1
  94. package/dist/common/extensibility.d.ts +27 -5
  95. package/dist/common/extensibility.js +1 -1
  96. package/dist/common/extensibility.js.map +1 -1
  97. package/dist/common/fileBasedCancellationUtils.js +5 -5
  98. package/dist/common/fileBasedCancellationUtils.js.map +1 -1
  99. package/dist/common/fileSystem.d.ts +12 -10
  100. package/dist/common/fileSystem.js.map +1 -1
  101. package/dist/common/fullAccessHost.d.ts +3 -3
  102. package/dist/common/fullAccessHost.js +6 -6
  103. package/dist/common/fullAccessHost.js.map +1 -1
  104. package/dist/common/pathUtils.d.ts +13 -13
  105. package/dist/common/pathUtils.js.map +1 -1
  106. package/dist/common/realFileSystem.js +12 -7
  107. package/dist/common/realFileSystem.js.map +1 -1
  108. package/dist/common/uriParser.d.ts +2 -2
  109. package/dist/common/uriParser.js +3 -3
  110. package/dist/common/uriParser.js.map +1 -1
  111. package/dist/common/workspaceEditUtils.d.ts +5 -5
  112. package/dist/common/workspaceEditUtils.js +7 -4
  113. package/dist/common/workspaceEditUtils.js.map +1 -1
  114. package/dist/languageServerBase.d.ts +32 -31
  115. package/dist/languageServerBase.js +278 -345
  116. package/dist/languageServerBase.js.map +1 -1
  117. package/dist/languageService/analyzerServiceExecutor.js +0 -1
  118. package/dist/languageService/analyzerServiceExecutor.js.map +1 -1
  119. package/dist/languageService/callHierarchyProvider.d.ts +16 -12
  120. package/dist/languageService/callHierarchyProvider.js +125 -41
  121. package/dist/languageService/callHierarchyProvider.js.map +1 -1
  122. package/dist/languageService/completionProvider.d.ts +10 -13
  123. package/dist/languageService/completionProvider.js +21 -10
  124. package/dist/languageService/completionProvider.js.map +1 -1
  125. package/dist/languageService/definitionProvider.d.ts +23 -9
  126. package/dist/languageService/definitionProvider.js +116 -91
  127. package/dist/languageService/definitionProvider.js.map +1 -1
  128. package/dist/languageService/documentHighlightProvider.d.ts +8 -3
  129. package/dist/languageService/documentHighlightProvider.js +17 -6
  130. package/dist/languageService/documentHighlightProvider.js.map +1 -1
  131. package/dist/languageService/documentSymbolCollector.d.ts +6 -7
  132. package/dist/languageService/documentSymbolCollector.js +21 -11
  133. package/dist/languageService/documentSymbolCollector.js.map +1 -1
  134. package/dist/languageService/documentSymbolProvider.js +5 -3
  135. package/dist/languageService/documentSymbolProvider.js.map +1 -1
  136. package/dist/languageService/hoverProvider.d.ts +4 -3
  137. package/dist/languageService/hoverProvider.js +30 -36
  138. package/dist/languageService/hoverProvider.js.map +1 -1
  139. package/dist/languageService/navigationUtils.d.ts +6 -0
  140. package/dist/languageService/navigationUtils.js +28 -0
  141. package/dist/languageService/navigationUtils.js.map +1 -0
  142. package/dist/languageService/quickActions.d.ts +2 -2
  143. package/dist/languageService/quickActions.js +12 -1
  144. package/dist/languageService/quickActions.js.map +1 -1
  145. package/dist/languageService/referencesProvider.d.ts +13 -12
  146. package/dist/languageService/referencesProvider.js +102 -16
  147. package/dist/languageService/referencesProvider.js.map +1 -1
  148. package/dist/languageService/renameProvider.d.ts +17 -0
  149. package/dist/languageService/renameProvider.js +143 -0
  150. package/dist/languageService/renameProvider.js.map +1 -0
  151. package/dist/languageService/signatureHelpProvider.d.ts +19 -23
  152. package/dist/languageService/signatureHelpProvider.js +111 -18
  153. package/dist/languageService/signatureHelpProvider.js.map +1 -1
  154. package/dist/localization/localize.d.ts +17 -14
  155. package/dist/localization/localize.js +4 -6
  156. package/dist/localization/localize.js.map +1 -1
  157. package/dist/localization/package.nls.en-us.json +5 -6
  158. package/dist/parser/characterStream.d.ts +3 -3
  159. package/dist/parser/characterStream.js +12 -12
  160. package/dist/parser/characterStream.js.map +1 -1
  161. package/dist/parser/parser.d.ts +1 -1
  162. package/dist/parser/parser.js.map +1 -1
  163. package/dist/pyright.js +57 -54
  164. package/dist/pyright.js.map +1 -1
  165. package/dist/pyrightFileSystem.d.ts +1 -1
  166. package/dist/pyrightFileSystem.js +21 -21
  167. package/dist/pyrightFileSystem.js.map +1 -1
  168. package/dist/readonlyAugmentedFileSystem.d.ts +6 -6
  169. package/dist/readonlyAugmentedFileSystem.js +28 -28
  170. package/dist/readonlyAugmentedFileSystem.js.map +1 -1
  171. package/dist/server.js +6 -6
  172. package/dist/server.js.map +1 -1
  173. package/dist/tests/chainedSourceFiles.test.js +1 -1
  174. package/dist/tests/chainedSourceFiles.test.js.map +1 -1
  175. package/dist/tests/checker.test.js +1 -1
  176. package/dist/tests/documentSymbolCollector.test.js +3 -3
  177. package/dist/tests/documentSymbolCollector.test.js.map +1 -1
  178. package/dist/tests/fourslash/fourslash.d.ts +4 -4
  179. package/dist/tests/fourslash/missingTypeStub.codeAction.fourslash.js +1 -1
  180. package/dist/tests/fourslash/missingTypeStub.codeAction.fourslash.js.map +1 -1
  181. package/dist/tests/harness/fourslash/testLanguageService.d.ts +3 -3
  182. package/dist/tests/harness/fourslash/testLanguageService.js.map +1 -1
  183. package/dist/tests/harness/fourslash/testState.d.ts +11 -11
  184. package/dist/tests/harness/fourslash/testState.js +123 -113
  185. package/dist/tests/harness/fourslash/testState.js.map +1 -1
  186. package/dist/tests/harness/vfs/filesystem.d.ts +8 -8
  187. package/dist/tests/harness/vfs/filesystem.js +68 -68
  188. package/dist/tests/harness/vfs/filesystem.js.map +1 -1
  189. package/dist/tests/importStatementUtils.test.js +34 -0
  190. package/dist/tests/importStatementUtils.test.js.map +1 -1
  191. package/dist/tests/signatureHelp.test.js +5 -1
  192. package/dist/tests/signatureHelp.test.js.map +1 -1
  193. package/dist/tests/sourceFile.test.js +2 -2
  194. package/dist/tests/sourceFile.test.js.map +1 -1
  195. package/dist/tests/testStateUtils.d.ts +2 -2
  196. package/dist/tests/testStateUtils.js +39 -9
  197. package/dist/tests/testStateUtils.js.map +1 -1
  198. package/dist/tests/typeEvaluator1.test.js +8 -0
  199. package/dist/tests/typeEvaluator1.test.js.map +1 -1
  200. package/dist/tests/typeEvaluator2.test.js +13 -1
  201. package/dist/tests/typeEvaluator2.test.js.map +1 -1
  202. package/dist/tests/typeEvaluator3.test.js +2 -2
  203. package/dist/tests/typeEvaluator4.test.js +9 -1
  204. package/dist/tests/typeEvaluator4.test.js.map +1 -1
  205. package/dist/tests/typeEvaluator5.test.js +4 -0
  206. package/dist/tests/typeEvaluator5.test.js.map +1 -1
  207. package/dist/tests/workspaceEditUtils.test.js +84 -0
  208. package/dist/tests/workspaceEditUtils.test.js.map +1 -1
  209. package/package.json +4 -4
  210. package/dist/languageService/indentationUtils.d.ts +0 -16
  211. package/dist/languageService/indentationUtils.js +0 -727
  212. package/dist/languageService/indentationUtils.js.map +0 -1
  213. package/dist/languageService/insertionPointUtils.d.ts +0 -9
  214. package/dist/languageService/insertionPointUtils.js +0 -132
  215. package/dist/languageService/insertionPointUtils.js.map +0 -1
  216. package/dist/languageService/renameModuleProvider.d.ts +0 -65
  217. package/dist/languageService/renameModuleProvider.js +0 -939
  218. package/dist/languageService/renameModuleProvider.js.map +0 -1
  219. package/dist/tests/indentationUtils.ptvs.test.d.ts +0 -1
  220. package/dist/tests/indentationUtils.ptvs.test.js +0 -324
  221. package/dist/tests/indentationUtils.ptvs.test.js.map +0 -1
  222. package/dist/tests/indentationUtils.reindent.test.d.ts +0 -1
  223. package/dist/tests/indentationUtils.reindent.test.js +0 -372
  224. package/dist/tests/indentationUtils.reindent.test.js.map +0 -1
  225. package/dist/tests/indentationUtils.test.d.ts +0 -1
  226. package/dist/tests/indentationUtils.test.js +0 -502
  227. package/dist/tests/indentationUtils.test.js.map +0 -1
  228. package/dist/tests/insertionPointUtils.test.d.ts +0 -1
  229. package/dist/tests/insertionPointUtils.test.js +0 -154
  230. package/dist/tests/insertionPointUtils.test.js.map +0 -1
  231. package/dist/tests/moveSymbol.importAdder.test.d.ts +0 -1
  232. package/dist/tests/moveSymbol.importAdder.test.js +0 -298
  233. package/dist/tests/moveSymbol.importAdder.test.js.map +0 -1
  234. package/dist/tests/moveSymbol.insertion.test.d.ts +0 -1
  235. package/dist/tests/moveSymbol.insertion.test.js +0 -537
  236. package/dist/tests/moveSymbol.insertion.test.js.map +0 -1
  237. package/dist/tests/moveSymbol.misc.test.d.ts +0 -1
  238. package/dist/tests/moveSymbol.misc.test.js +0 -169
  239. package/dist/tests/moveSymbol.misc.test.js.map +0 -1
  240. package/dist/tests/moveSymbol.updateReference.test.d.ts +0 -1
  241. package/dist/tests/moveSymbol.updateReference.test.js +0 -1057
  242. package/dist/tests/moveSymbol.updateReference.test.js.map +0 -1
  243. package/dist/tests/renameModule.folder.test.d.ts +0 -1
  244. package/dist/tests/renameModule.folder.test.js +0 -229
  245. package/dist/tests/renameModule.folder.test.js.map +0 -1
  246. package/dist/tests/renameModule.fromImports.test.d.ts +0 -1
  247. package/dist/tests/renameModule.fromImports.test.js +0 -790
  248. package/dist/tests/renameModule.fromImports.test.js.map +0 -1
  249. package/dist/tests/renameModule.imports.test.d.ts +0 -1
  250. package/dist/tests/renameModule.imports.test.js +0 -380
  251. package/dist/tests/renameModule.imports.test.js.map +0 -1
  252. package/dist/tests/renameModule.misc.test.d.ts +0 -1
  253. package/dist/tests/renameModule.misc.test.js +0 -615
  254. package/dist/tests/renameModule.misc.test.js.map +0 -1
  255. package/dist/tests/renameModule.relativePath.test.d.ts +0 -1
  256. package/dist/tests/renameModule.relativePath.test.js +0 -231
  257. package/dist/tests/renameModule.relativePath.test.js.map +0 -1
  258. package/dist/tests/renameModuleTestUtils.d.ts +0 -4
  259. package/dist/tests/renameModuleTestUtils.js +0 -76
  260. package/dist/tests/renameModuleTestUtils.js.map +0 -1
@@ -31,6 +31,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.Program = exports.MaxWorkspaceIndexFileCount = void 0;
32
32
  const vscode_languageserver_1 = require("vscode-languageserver");
33
33
  const vscode_languageserver_types_1 = require("vscode-languageserver-types");
34
+ const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
34
35
  const cancellationUtils_1 = require("../common/cancellationUtils");
35
36
  const collectionUtils_1 = require("../common/collectionUtils");
36
37
  const configOptions_1 = require("../common/configOptions");
@@ -42,24 +43,13 @@ const logTracker_1 = require("../common/logTracker");
42
43
  const pathUtils_1 = require("../common/pathUtils");
43
44
  const positionUtils_1 = require("../common/positionUtils");
44
45
  const stringUtils_1 = require("../common/stringUtils");
45
- const textEditTracker_1 = require("../common/textEditTracker");
46
46
  const textRange_1 = require("../common/textRange");
47
- const textRangeCollection_1 = require("../common/textRangeCollection");
48
47
  const timing_1 = require("../common/timing");
49
- const workspaceEditUtils_1 = require("../common/workspaceEditUtils");
50
48
  const autoImporter_1 = require("../languageService/autoImporter");
51
- const callHierarchyProvider_1 = require("../languageService/callHierarchyProvider");
52
49
  const completionProvider_1 = require("../languageService/completionProvider");
53
- const documentSymbolCollector_1 = require("../languageService/documentSymbolCollector");
54
- const importAdder_1 = require("../languageService/importAdder");
55
- const indentationUtils_1 = require("../languageService/indentationUtils");
56
- const insertionPointUtils_1 = require("../languageService/insertionPointUtils");
57
- const referencesProvider_1 = require("../languageService/referencesProvider");
58
- const renameModuleProvider_1 = require("../languageService/renameModuleProvider");
59
50
  const AnalyzerNodeInfo = __importStar(require("./analyzerNodeInfo"));
60
51
  const cacheManager_1 = require("./cacheManager");
61
52
  const circularDependency_1 = require("./circularDependency");
62
- const declaration_1 = require("./declaration");
63
53
  const parseTreeUtils_1 = require("./parseTreeUtils");
64
54
  const scopeUtils_1 = require("./scopeUtils");
65
55
  const sourceFile_1 = require("./sourceFile");
@@ -83,7 +73,8 @@ class Program {
83
73
  this._sourceFileList = [];
84
74
  this._sourceFileMap = new Map();
85
75
  this._parsedFileCount = 0;
86
- this._lookUpImport = (filePathOrModule) => {
76
+ this._isEditMode = false;
77
+ this._lookUpImport = (filePathOrModule, options) => {
87
78
  var _a;
88
79
  let sourceFileInfo;
89
80
  if (typeof filePathOrModule === 'string') {
@@ -102,7 +93,7 @@ class Program {
102
93
  // See if the source file already exists in the program.
103
94
  sourceFileInfo = this.getSourceFileInfo(resolvedPath);
104
95
  if (!sourceFileInfo) {
105
- resolvedPath = (0, pathUtils_1.normalizePathCase)(this._fs, resolvedPath);
96
+ resolvedPath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, resolvedPath);
106
97
  // Start tracking the source file.
107
98
  this.addTrackedFile(resolvedPath);
108
99
  sourceFileInfo = this.getSourceFileInfo(resolvedPath);
@@ -117,7 +108,7 @@ class Program {
117
108
  // Bind the file if it's not already bound. Don't count this time
118
109
  // against the type checker.
119
110
  timing_1.timingStats.typeCheckerTime.subtractFromTime(() => {
120
- this._bindFile(sourceFileInfo);
111
+ this._bindFile(sourceFileInfo, /* content */ undefined, options === null || options === void 0 ? void 0 : options.skipFileNeededCheck);
121
112
  });
122
113
  }
123
114
  const symbolTable = sourceFileInfo.sourceFile.getModuleSymbolTable();
@@ -148,9 +139,6 @@ class Program {
148
139
  this._id = Program._nextId;
149
140
  Program._nextId += 1;
150
141
  }
151
- dispose() {
152
- this._cacheManager.unregisterCacheOwner(this);
153
- }
154
142
  get id() {
155
143
  return this._id;
156
144
  }
@@ -169,6 +157,47 @@ class Program {
169
157
  get importResolver() {
170
158
  return this._importResolver;
171
159
  }
160
+ get fileSystem() {
161
+ return this._importResolver.fileSystem;
162
+ }
163
+ dispose() {
164
+ this._cacheManager.unregisterCacheOwner(this);
165
+ }
166
+ enterEditMode() {
167
+ // Keep track of edit mode so we can apply it to new source files.
168
+ this._isEditMode = true;
169
+ // Tell all source files we're in edit mode.
170
+ this._sourceFileList.forEach((sourceFile) => {
171
+ sourceFile.sourceFile.enterEditMode();
172
+ });
173
+ }
174
+ exitEditMode() {
175
+ // Tell all source files we're no longer in edit mode. Gather
176
+ // up all of their edits.
177
+ const edits = [];
178
+ this._sourceFileList.forEach((sourceFile) => {
179
+ const newContents = sourceFile.sourceFile.exitEditMode();
180
+ if (newContents) {
181
+ // This means this source file was modified. We need to recompute its imports after
182
+ // we put it back to how it was.
183
+ this._updateSourceFileImports(sourceFile, this.configOptions);
184
+ // Create a text document so we can compute the edits.
185
+ const textDocument = vscode_languageserver_textdocument_1.TextDocument.create(sourceFile.sourceFile.getFilePath(), 'python', 1, sourceFile.sourceFile.getFileContent() || '');
186
+ // Add an edit action to the list.
187
+ edits.push({
188
+ filePath: sourceFile.sourceFile.getFilePath(),
189
+ range: {
190
+ start: { line: 0, character: 0 },
191
+ end: { line: textDocument.lineCount, character: 0 },
192
+ },
193
+ replacementText: newContents,
194
+ });
195
+ }
196
+ });
197
+ // Stop applying edit mode to new source files.
198
+ this._isEditMode = false;
199
+ return edits;
200
+ }
172
201
  setConfigOptions(configOptions) {
173
202
  this._configOptions = configOptions;
174
203
  this._importResolver.setConfigOptions(configOptions);
@@ -188,12 +217,12 @@ class Program {
188
217
  // We need to determine which files to remove from the existing file list.
189
218
  const newFileMap = new Map();
190
219
  filePaths.forEach((path) => {
191
- newFileMap.set((0, pathUtils_1.normalizePathCase)(this._fs, path), path);
220
+ newFileMap.set((0, pathUtils_1.normalizePathCase)(this.fileSystem, path), path);
192
221
  });
193
222
  // Files that are not in the tracked file list are
194
223
  // marked as no longer tracked.
195
224
  this._sourceFileList.forEach((oldFile) => {
196
- const filePath = (0, pathUtils_1.normalizePathCase)(this._fs, oldFile.sourceFile.getFilePath());
225
+ const filePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, oldFile.sourceFile.getFilePath());
197
226
  if (!newFileMap.has(filePath)) {
198
227
  oldFile.isTracked = false;
199
228
  }
@@ -240,7 +269,7 @@ class Program {
240
269
  sourceFileInfo.isTracked = true;
241
270
  return sourceFileInfo.sourceFile;
242
271
  }
243
- const sourceFile = new sourceFile_1.SourceFile(this._fs, filePath, importName, isThirdPartyImport, isInPyTypedPackage, this._console, this._logTracker);
272
+ const sourceFile = new sourceFile_1.SourceFile(this.fileSystem, filePath, importName, isThirdPartyImport, isInPyTypedPackage, this._isEditMode, this._console, this._logTracker);
244
273
  sourceFileInfo = {
245
274
  sourceFile,
246
275
  isTracked: true,
@@ -262,9 +291,9 @@ class Program {
262
291
  let sourceFileInfo = this.getSourceFileInfo(filePath);
263
292
  if (!sourceFileInfo) {
264
293
  const importName = this._getImportNameForFile(filePath);
265
- const sourceFile = new sourceFile_1.SourceFile(this._fs, filePath, importName,
294
+ const sourceFile = new sourceFile_1.SourceFile(this.fileSystem, filePath, importName,
266
295
  /* isThirdPartyImport */ false,
267
- /* isInPyTypedPackage */ false, this._console, this._logTracker, options === null || options === void 0 ? void 0 : options.realFilePath, (_a = options === null || options === void 0 ? void 0 : options.ipythonMode) !== null && _a !== void 0 ? _a : sourceFile_1.IPythonMode.None);
296
+ /* isInPyTypedPackage */ false, this._isEditMode, this._console, this._logTracker, options === null || options === void 0 ? void 0 : options.realFilePath, (_a = options === null || options === void 0 ? void 0 : options.ipythonMode) !== null && _a !== void 0 ? _a : sourceFile_1.IPythonMode.None);
268
297
  const chainedFilePath = options === null || options === void 0 ? void 0 : options.chainedFilePath;
269
298
  sourceFileInfo = {
270
299
  sourceFile,
@@ -310,7 +339,7 @@ class Program {
310
339
  if (sourceFileInfo) {
311
340
  sourceFileInfo.isOpenByClient = false;
312
341
  sourceFileInfo.isTracked = isTracked !== null && isTracked !== void 0 ? isTracked : sourceFileInfo.isTracked;
313
- sourceFileInfo.sourceFile.setClientVersion(null, []);
342
+ sourceFileInfo.sourceFile.setClientVersion(null, '');
314
343
  // There is no guarantee that content is saved before the file is closed.
315
344
  // We need to mark the file dirty so we can re-analyze next time.
316
345
  // This won't matter much for OpenFileOnly users, but it will matter for
@@ -405,7 +434,7 @@ class Program {
405
434
  return this._configOptions.functionSignatureDisplay;
406
435
  }
407
436
  containsSourceFileIn(folder) {
408
- const normalized = (0, pathUtils_1.normalizePathCase)(this._fs, folder);
437
+ const normalized = (0, pathUtils_1.normalizePathCase)(this.fileSystem, folder);
409
438
  return this._sourceFileList.some((i) => i.sourceFile.getFilePath().startsWith(normalized));
410
439
  }
411
440
  owns(filePath) {
@@ -428,8 +457,11 @@ class Program {
428
457
  var _a;
429
458
  return (_a = this.getBoundSourceFileInfo(filePath)) === null || _a === void 0 ? void 0 : _a.sourceFile;
430
459
  }
460
+ getSourceFileInfoList() {
461
+ return this._sourceFileList;
462
+ }
431
463
  getSourceFileInfo(filePath) {
432
- return this._sourceFileMap.get((0, pathUtils_1.normalizePathCase)(this._fs, filePath));
464
+ return this._sourceFileMap.get((0, pathUtils_1.normalizePathCase)(this.fileSystem, filePath));
433
465
  }
434
466
  getBoundSourceFileInfo(filePath, content, force) {
435
467
  const sourceFileInfo = this.getSourceFileInfo(filePath);
@@ -504,13 +536,20 @@ class Program {
504
536
  // _handleMemoryHighUsage will make sure we don't OOM and
505
537
  // at the end of this method, we will drop all trees and symbol tables
506
538
  // created due to indexing.
539
+ let count = 0;
507
540
  const initiallyParsedSet = new Set();
508
541
  for (const sourceFileInfo of this._sourceFileList) {
509
542
  if (!sourceFileInfo.sourceFile.isParseRequired()) {
510
543
  initiallyParsedSet.add(sourceFileInfo);
511
544
  }
545
+ if ((0, sourceFileInfoUtils_1.isUserCode)(sourceFileInfo) && !sourceFileInfo.sourceFile.isIndexingRequired()) {
546
+ count++;
547
+ }
548
+ }
549
+ if (count >= exports.MaxWorkspaceIndexFileCount) {
550
+ // Already processed max files.
551
+ return 0;
512
552
  }
513
- let count = 0;
514
553
  for (const sourceFileInfo of this._sourceFileList) {
515
554
  if (!(0, sourceFileInfoUtils_1.isUserCode)(sourceFileInfo) || !sourceFileInfo.sourceFile.isIndexingRequired()) {
516
555
  continue;
@@ -556,6 +595,9 @@ class Program {
556
595
  var _a;
557
596
  return (_a = this.getBoundSourceFile(filePath)) === null || _a === void 0 ? void 0 : _a.getParseResults();
558
597
  }
598
+ handleMemoryHighUsage() {
599
+ this._handleMemoryHighUsage();
600
+ }
559
601
  // Prints a detailed list of files that have been checked and the times associated
560
602
  // with each of them, sorted greatest to least.
561
603
  printDetailedAnalysisTimes() {
@@ -636,7 +678,7 @@ class Program {
636
678
  }
637
679
  const typeStubDir = (0, pathUtils_1.getDirectoryPath)(typeStubPath);
638
680
  try {
639
- (0, pathUtils_1.makeDirectories)(this._fs, typeStubDir, stubPath);
681
+ (0, pathUtils_1.makeDirectories)(this.fileSystem, typeStubDir, stubPath);
640
682
  }
641
683
  catch (e) {
642
684
  const errMsg = `Could not create directory for '${typeStubDir}'`;
@@ -663,1763 +705,1122 @@ class Program {
663
705
  const evaluator = this._evaluator || this._createNewEvaluator();
664
706
  return evaluator.printType(type, options);
665
707
  }
666
- static _getPrintTypeFlags(configOptions) {
667
- let flags = 0 /* None */;
668
- if (configOptions.diagnosticRuleSet.printUnknownAsAny) {
669
- flags |= 1 /* PrintUnknownWithAny */;
670
- }
671
- if (configOptions.diagnosticRuleSet.omitConditionalConstraint) {
672
- flags |= 64 /* OmitConditionalConstraint */;
673
- }
674
- if (configOptions.diagnosticRuleSet.omitTypeArgsIfUnknown) {
675
- flags |= 2 /* OmitTypeArgumentsIfUnknown */;
676
- }
677
- if (configOptions.diagnosticRuleSet.omitUnannotatedParamType) {
678
- flags |= 4 /* OmitUnannotatedParamType */;
708
+ getTextOnRange(filePath, range, token) {
709
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
710
+ if (!sourceFileInfo) {
711
+ return undefined;
679
712
  }
680
- if (configOptions.diagnosticRuleSet.pep604Printing) {
681
- flags |= 8 /* PEP604 */;
713
+ const sourceFile = sourceFileInfo.sourceFile;
714
+ const fileContents = sourceFile.getOpenFileContents();
715
+ if (fileContents === undefined) {
716
+ // this only works with opened file
717
+ return undefined;
682
718
  }
683
- return flags;
684
- }
685
- get _fs() {
686
- return this._importResolver.fileSystem;
687
- }
688
- _getImportNameForFile(filePath) {
689
- // We allow illegal module names (e.g. names that include "-" in them)
690
- // because we want a unique name for each module even if it cannot be
691
- // imported through an "import" statement. It's important to have a
692
- // unique name in case two modules declare types with the same local
693
- // name. The type checker uses the fully-qualified (unique) module name
694
- // to differentiate between such types.
695
- const moduleNameAndType = this._importResolver.getModuleNameForImport(filePath, this._configOptions.getDefaultExecEnvironment(),
696
- /* allowIllegalModuleName */ true);
697
- return moduleNameAndType.moduleName;
719
+ return this._runEvaluatorWithCancellationToken(token, () => {
720
+ this._parseFile(sourceFileInfo);
721
+ const parseTree = sourceFile.getParseResults();
722
+ const textRange = (0, positionUtils_1.convertRangeToTextRange)(range, parseTree.tokenizerOutput.lines);
723
+ if (!textRange) {
724
+ return undefined;
725
+ }
726
+ return fileContents.substr(textRange.start, textRange.length);
727
+ });
698
728
  }
699
- // A "shadowed" file is a python source file that has been added to the program because
700
- // it "shadows" a type stub file for purposes of finding doc strings and definitions.
701
- // We need to track the relationship so if the original type stub is removed from the
702
- // program, we can remove the corresponding shadowed file and any files it imports.
703
- _addShadowedFile(stubFile, shadowImplPath) {
704
- let shadowFileInfo = this.getSourceFileInfo(shadowImplPath);
705
- if (!shadowFileInfo) {
706
- shadowFileInfo = this.addInterimFile(shadowImplPath);
707
- }
708
- if (!shadowFileInfo.shadows.includes(stubFile)) {
709
- shadowFileInfo.shadows.push(stubFile);
729
+ getAutoImports(filePath, range, similarityLimit, nameMap, options, token) {
730
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
731
+ if (!sourceFileInfo) {
732
+ return [];
710
733
  }
711
- if (!stubFile.shadowedBy.includes(shadowFileInfo)) {
712
- stubFile.shadowedBy.push(shadowFileInfo);
734
+ const sourceFile = sourceFileInfo.sourceFile;
735
+ const fileContents = sourceFile.getOpenFileContents();
736
+ if (fileContents === undefined) {
737
+ // this only works with opened file
738
+ return [];
713
739
  }
714
- return shadowFileInfo.sourceFile;
715
- }
716
- _createInterimFileInfo(filePath) {
717
- const importName = this._getImportNameForFile(filePath);
718
- const sourceFile = new sourceFile_1.SourceFile(this._fs, filePath, importName,
719
- /* isThirdPartyImport */ false,
720
- /* isInPyTypedPackage */ false, this._console, this._logTracker);
721
- const sourceFileInfo = {
722
- sourceFile,
723
- isTracked: false,
724
- isOpenByClient: false,
725
- isTypeshedFile: false,
726
- isThirdPartyImport: false,
727
- isThirdPartyPyTypedPresent: false,
728
- diagnosticsVersion: undefined,
729
- imports: [],
730
- importedBy: [],
731
- shadows: [],
732
- shadowedBy: [],
733
- };
734
- return sourceFileInfo;
740
+ return this._runEvaluatorWithCancellationToken(token, () => {
741
+ var _a;
742
+ this._bindFile(sourceFileInfo);
743
+ const parseTree = sourceFile.getParseResults();
744
+ const textRange = (0, positionUtils_1.convertRangeToTextRange)(range, parseTree.tokenizerOutput.lines);
745
+ if (!textRange) {
746
+ return [];
747
+ }
748
+ const currentNode = (0, parseTreeUtils_1.findNodeByOffset)(parseTree.parseTree, textRange.start);
749
+ if (!currentNode) {
750
+ return [];
751
+ }
752
+ const writtenWord = fileContents.substr(textRange.start, textRange.length);
753
+ const map = this._buildModuleSymbolsMap(sourceFileInfo, options.libraryMap,
754
+ /* includeSymbolsFromIndices */ true, token);
755
+ options.patternMatcher =
756
+ (_a = options.patternMatcher) !== null && _a !== void 0 ? _a : ((p, t) => (0, stringUtils_1.computeCompletionSimilarity)(p, t) > similarityLimit);
757
+ const autoImporter = new autoImporter_1.AutoImporter(this._configOptions.findExecEnvironment(filePath), this._importResolver, parseTree, range.start, new completionProvider_1.CompletionMap(), map, options);
758
+ // Filter out any name that is already defined in the current scope.
759
+ const results = [];
760
+ const currentScope = (0, scopeUtils_1.getScopeForNode)(currentNode);
761
+ if (currentScope) {
762
+ const info = nameMap === null || nameMap === void 0 ? void 0 : nameMap.get(writtenWord);
763
+ if (info) {
764
+ // No scope filter is needed since we only do exact match.
765
+ (0, collectionUtils_1.appendArray)(results, autoImporter.getAutoImportCandidatesForAbbr(writtenWord, info, token));
766
+ }
767
+ results.push(...autoImporter
768
+ .getAutoImportCandidates(writtenWord, similarityLimit, /* abbrFromUsers */ undefined, token)
769
+ .filter((r) => !currentScope.lookUpSymbolRecursive(r.name)));
770
+ }
771
+ return results;
772
+ });
735
773
  }
736
- _createNewEvaluator() {
737
- if (this._evaluator) {
738
- // We shouldn't need to call this, but there appears to be a bug
739
- // in the v8 garbage collector where it's unable to resolve orphaned
740
- // objects without us giving it some assistance.
741
- this._evaluator.disposeEvaluator();
742
- }
743
- this._evaluator = (0, typeEvaluatorWithTracker_1.createTypeEvaluatorWithTracker)(this._lookUpImport, {
744
- printTypeFlags: Program._getPrintTypeFlags(this._configOptions),
745
- logCalls: this._configOptions.logTypeEvaluationTime,
746
- minimumLoggingThreshold: this._configOptions.typeEvaluationTimeThreshold,
747
- evaluateUnknownImportsAsAny: !!this._configOptions.evaluateUnknownImportsAsAny,
748
- verifyTypeCacheEvaluatorFlags: !!this._configOptions.internalTestMode,
749
- }, this._logTracker, this._configOptions.logTypeEvaluationTime
750
- ? (0, tracePrinter_1.createTracePrinter)(this._importResolver.getImportRoots(this._configOptions.findExecEnvironment(this._configOptions.projectRoot)))
751
- : undefined);
752
- return this._evaluator;
774
+ getDiagnostics(options) {
775
+ const fileDiagnostics = this._removeUnneededFiles();
776
+ this._sourceFileList.forEach((sourceFileInfo) => {
777
+ if (this._shouldCheckFile(sourceFileInfo)) {
778
+ const diagnostics = sourceFileInfo.sourceFile.getDiagnostics(options, sourceFileInfo.diagnosticsVersion);
779
+ if (diagnostics !== undefined) {
780
+ fileDiagnostics.push({
781
+ filePath: sourceFileInfo.sourceFile.getFilePath(),
782
+ version: sourceFileInfo.sourceFile.getClientVersion(),
783
+ diagnostics,
784
+ });
785
+ // Update the cached diagnosticsVersion so we can determine
786
+ // whether there are any updates next time we call getDiagnostics.
787
+ sourceFileInfo.diagnosticsVersion = sourceFileInfo.sourceFile.getDiagnosticVersion();
788
+ }
789
+ }
790
+ else if (!sourceFileInfo.isOpenByClient &&
791
+ options.checkOnlyOpenFiles &&
792
+ sourceFileInfo.diagnosticsVersion !== undefined) {
793
+ // This condition occurs when the user switches from workspace to
794
+ // "open files only" mode. Clear all diagnostics for this file.
795
+ fileDiagnostics.push({
796
+ filePath: sourceFileInfo.sourceFile.getFilePath(),
797
+ version: sourceFileInfo.sourceFile.getClientVersion(),
798
+ diagnostics: [],
799
+ });
800
+ sourceFileInfo.diagnosticsVersion = undefined;
801
+ }
802
+ });
803
+ return fileDiagnostics;
753
804
  }
754
- _parseFile(fileToParse, content, force) {
755
- if (!force && (!this._isFileNeeded(fileToParse) || !fileToParse.sourceFile.isParseRequired())) {
756
- return;
757
- }
758
- // SourceFile.parse should only be called here in the program, as calling it
759
- // elsewhere could break the entire dependency graph maintained by the program.
760
- // Other parts of the program should use _parseFile to create ParseResults from
761
- // the sourceFile. For standalone parseResults, use parseFile or the Parser directly.
762
- if (fileToParse.sourceFile.parse(this._configOptions, this._importResolver, content)) {
763
- this._parsedFileCount++;
764
- this._updateSourceFileImports(fileToParse, this._configOptions);
805
+ getDiagnosticsForRange(filePath, range) {
806
+ const sourceFile = this.getSourceFile(filePath);
807
+ if (!sourceFile) {
808
+ return [];
765
809
  }
766
- if (fileToParse.sourceFile.isFileDeleted()) {
767
- fileToParse.isTracked = false;
768
- // Mark any files that depend on this file as dirty
769
- // also. This will retrigger analysis of these other files.
770
- const markDirtySet = new Set();
771
- this._markFileDirtyRecursive(fileToParse, markDirtySet);
772
- // Invalidate the import resolver's cache as well.
773
- this._importResolver.invalidateCache();
810
+ const unfilteredDiagnostics = sourceFile.getDiagnostics(this._configOptions);
811
+ if (!unfilteredDiagnostics) {
812
+ return [];
774
813
  }
814
+ return unfilteredDiagnostics.filter((diag) => {
815
+ return (0, textRange_1.doRangesIntersect)(diag.range, range);
816
+ });
775
817
  }
776
- _getImplicitImports(file) {
777
- var _a, _b;
778
- // If file is not parsed, then chainedSourceFile, ipythonDisplayImport,
779
- // builtinsImport might not exist or incorrect.
780
- // They will be added when _parseFile is called and _updateSourceFileImports ran.
781
- if (file.builtinsImport === file) {
782
- return undefined;
783
- }
784
- const tryReturn = (input) => {
785
- if (!input || input.sourceFile.isFileDeleted()) {
818
+ getFileIndex(filePath, options, token) {
819
+ if (options.indexingForAutoImportMode) {
820
+ // Memory optimization. We only want to hold onto symbols
821
+ // usable outside when importSymbolsOnly is on.
822
+ const name = (0, pathUtils_1.stripFileExtension)((0, pathUtils_1.getFileName)(filePath));
823
+ if ((0, symbolNameUtils_1.isPrivateOrProtectedName)(name)) {
786
824
  return undefined;
787
825
  }
788
- return input;
789
- };
790
- return (_b = (_a = tryReturn(file.chainedSourceFile)) !== null && _a !== void 0 ? _a : tryReturn(file.ipythonDisplayImport)) !== null && _b !== void 0 ? _b : file.builtinsImport;
791
- }
792
- _bindImplicitImports(fileToAnalyze) {
793
- // Get all of the potential imports for this file.
794
- const implicitImports = [];
795
- const implicitSet = new Set();
796
- let nextImplicitImport = this._getImplicitImports(fileToAnalyze);
797
- while (nextImplicitImport) {
798
- const implicitPath = nextImplicitImport.sourceFile.getFilePath();
799
- if (implicitSet.has(implicitPath)) {
800
- // We've found a cycle. Break out of the loop.
801
- debug.fail(`Found a cycle in implicit imports files for ${implicitPath}`);
802
- }
803
- implicitSet.add(implicitPath);
804
- implicitImports.push(nextImplicitImport);
805
- this._parseFile(nextImplicitImport);
806
- nextImplicitImport = this._getImplicitImports(nextImplicitImport);
807
826
  }
808
- if (implicitImports.length === 0) {
809
- return;
810
- }
811
- // Go in reverse order (so top of chain is first).
812
- let implicitImport = implicitImports.pop();
813
- while (implicitImport) {
814
- // Bind this file, but don't recurse into its imports.
815
- this._bindFile(implicitImport, undefined, undefined, /* isImplicitImport */ true);
816
- implicitImport = implicitImports.pop();
817
- }
818
- }
819
- // Binds the specified file and all of its dependencies, recursively. If
820
- // it runs out of time, it returns true. If it completes, it returns false.
821
- _bindFile(fileToAnalyze, content, force, isImplicitImport) {
822
- var _a, _b;
823
- if (!force && (!this._isFileNeeded(fileToAnalyze) || !fileToAnalyze.sourceFile.isBindingRequired())) {
824
- return;
825
- }
826
- this._parseFile(fileToAnalyze, content, force);
827
- // Create a function to get the scope info.
828
- const getScopeIfAvailable = (fileInfo) => {
829
- if (!fileInfo || fileInfo === fileToAnalyze) {
827
+ this._handleMemoryHighUsage();
828
+ return this._runEvaluatorWithCancellationToken(token, () => {
829
+ var _a;
830
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
831
+ if (!sourceFileInfo) {
830
832
  return undefined;
831
833
  }
832
- // If the file was deleted, there's no scope to return.
833
- if (fileInfo.sourceFile.isFileDeleted()) {
834
- return undefined;
834
+ const content = (_a = sourceFileInfo.sourceFile.getFileContent()) !== null && _a !== void 0 ? _a : '';
835
+ if (options.indexingForAutoImportMode &&
836
+ !options.includeAllSymbols &&
837
+ !sourceFileInfo.sourceFile.isStubFile() &&
838
+ !sourceFileInfo.sourceFile.isThirdPartyPyTypedPresent()) {
839
+ // Perf optimization. if py file doesn't contain __all__
840
+ // No need to parse and bind.
841
+ if (content.indexOf('__all__') < 0) {
842
+ return undefined;
843
+ }
835
844
  }
836
- const parseResults = fileInfo.sourceFile.getParseResults();
837
- if (!parseResults) {
838
- return undefined;
845
+ this._bindFile(sourceFileInfo, content);
846
+ return sourceFileInfo.sourceFile.index(options, token);
847
+ });
848
+ }
849
+ addSymbolsForDocument(filePath, symbolList, token) {
850
+ return this._runEvaluatorWithCancellationToken(token, () => {
851
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
852
+ if (sourceFileInfo) {
853
+ if (!sourceFileInfo.sourceFile.getCachedIndexResults()) {
854
+ // If we already have cached index for this file, no need to bind this file.
855
+ this._bindFile(sourceFileInfo);
856
+ }
857
+ sourceFileInfo.sourceFile.addHierarchicalSymbolsForDocument(symbolList, token);
839
858
  }
840
- // File should already be bound because of the chained file binding above.
841
- const scope = AnalyzerNodeInfo.getScope(parseResults.parseTree);
842
- return scope;
843
- };
844
- let builtinsScope;
845
- if (fileToAnalyze.builtinsImport && fileToAnalyze.builtinsImport !== fileToAnalyze) {
846
- // Bind all of the implicit imports first. So we don't recurse into them.
847
- if (!isImplicitImport) {
848
- this._bindImplicitImports(fileToAnalyze);
859
+ });
860
+ }
861
+ reportSymbolsForWorkspace(query, reporter, token) {
862
+ this._runEvaluatorWithCancellationToken(token, () => {
863
+ // Don't do a search if the query is empty. We'll return
864
+ // too many results in this case.
865
+ if (!query) {
866
+ return;
849
867
  }
850
- // If it is not builtin module itself, we need to parse and bind
851
- // the ipython display import if required. Otherwise, get builtin module.
852
- builtinsScope =
853
- (_b = (_a = getScopeIfAvailable(fileToAnalyze.chainedSourceFile)) !== null && _a !== void 0 ? _a : getScopeIfAvailable(fileToAnalyze.ipythonDisplayImport)) !== null && _b !== void 0 ? _b : getScopeIfAvailable(fileToAnalyze.builtinsImport);
868
+ // "Workspace symbols" searches symbols only from user code.
869
+ for (const sourceFileInfo of this._sourceFileList) {
870
+ if (!(0, sourceFileInfoUtils_1.isUserCode)(sourceFileInfo)) {
871
+ continue;
872
+ }
873
+ if (!sourceFileInfo.sourceFile.getCachedIndexResults()) {
874
+ // If we already have cached index for this file, no need to bind this file.
875
+ this._bindFile(sourceFileInfo);
876
+ }
877
+ const symbolList = sourceFileInfo.sourceFile.getSymbolsForDocument(query, token);
878
+ if (symbolList.length > 0) {
879
+ reporter(symbolList);
880
+ }
881
+ // This operation can consume significant memory, so check
882
+ // for situations where we need to discard the type cache.
883
+ this._handleMemoryHighUsage();
884
+ }
885
+ });
886
+ }
887
+ async getCompletionsForPosition(filePath, position, workspacePath, options, nameMap, libraryMap, token) {
888
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
889
+ if (!sourceFileInfo) {
890
+ return undefined;
854
891
  }
855
- let futureImports = fileToAnalyze.sourceFile.getParseResults().futureImports;
856
- if (fileToAnalyze.chainedSourceFile) {
857
- futureImports = this._getEffectiveFutureImports(futureImports, fileToAnalyze.chainedSourceFile);
892
+ const completionResult = this._logTracker.log(`completion at ${filePath}:${position.line}:${position.character}`, (ls) => {
893
+ var _a;
894
+ const result = this._runEvaluatorWithCancellationToken(token, () => {
895
+ this._bindFile(sourceFileInfo);
896
+ return sourceFileInfo.sourceFile.getCompletionsForPosition(this, position, workspacePath, this._lookUpImport, options, nameMap, libraryMap, () => this._buildModuleSymbolsMap(sourceFileInfo, libraryMap, options.includeUserSymbolsInAutoImport, token), token);
897
+ });
898
+ ls.add(`found ${(_a = result === null || result === void 0 ? void 0 : result.completionMap.size) !== null && _a !== void 0 ? _a : 'null'} items`);
899
+ return result;
900
+ });
901
+ const completionResultsList = {
902
+ completionList: vscode_languageserver_types_1.CompletionList.create(completionResult === null || completionResult === void 0 ? void 0 : completionResult.completionMap.toArray()),
903
+ memberAccessInfo: completionResult === null || completionResult === void 0 ? void 0 : completionResult.memberAccessInfo,
904
+ autoImportInfo: completionResult === null || completionResult === void 0 ? void 0 : completionResult.autoImportInfo,
905
+ extensionInfo: completionResult === null || completionResult === void 0 ? void 0 : completionResult.extensionInfo,
906
+ };
907
+ const parseResults = sourceFileInfo.sourceFile.getParseResults();
908
+ if ((parseResults === null || parseResults === void 0 ? void 0 : parseResults.parseTree) && (parseResults === null || parseResults === void 0 ? void 0 : parseResults.text)) {
909
+ const execEnv = this._configOptions.findExecEnvironment(filePath);
910
+ const sourceMapper = this._createSourceMapper(execEnv, token, sourceFileInfo,
911
+ /* mapCompiled */ true);
912
+ const offset = (0, positionUtils_1.convertPositionToOffset)(position, parseResults.tokenizerOutput.lines);
913
+ if (offset !== undefined && sourceMapper) {
914
+ await Promise.all(extensibility_1.Extensions.getProgramExtensions(parseResults.parseTree).map((e) => {
915
+ var _a;
916
+ return (_a = e.completionListExtension) === null || _a === void 0 ? void 0 : _a.updateCompletionResults(this.evaluator, sourceMapper, options, completionResultsList, parseResults, offset, this._configOptions.functionSignatureDisplay, token);
917
+ }));
918
+ }
858
919
  }
859
- fileToAnalyze.effectiveFutureImports = futureImports.size > 0 ? futureImports : undefined;
860
- fileToAnalyze.sourceFile.bind(this._configOptions, this._lookUpImport, builtinsScope, futureImports);
920
+ return completionResultsList;
861
921
  }
862
- _getEffectiveFutureImports(futureImports, chainedSourceFile) {
863
- var _a;
864
- const effectiveFutureImports = new Set(futureImports);
865
- (_a = chainedSourceFile.effectiveFutureImports) === null || _a === void 0 ? void 0 : _a.forEach((value) => {
866
- effectiveFutureImports.add(value);
922
+ resolveCompletionItem(filePath, completionItem, options, nameMap, libraryMap, token) {
923
+ return this._runEvaluatorWithCancellationToken(token, () => {
924
+ const sourceFileInfo = this.getSourceFileInfo(filePath);
925
+ if (!sourceFileInfo) {
926
+ return;
927
+ }
928
+ this._bindFile(sourceFileInfo);
929
+ sourceFileInfo.sourceFile.resolveCompletionItem(this, this._lookUpImport, options, nameMap, libraryMap, () => this._buildModuleSymbolsMap(sourceFileInfo, libraryMap, options.includeUserSymbolsInAutoImport, token), completionItem, token);
867
930
  });
868
- return effectiveFutureImports;
869
931
  }
870
- // Build a map of all modules within this program and the module-
871
- // level scope that contains the symbol table for the module.
872
- _buildModuleSymbolsMap(sourceFileToExclude, libraryMap, includeSymbolsFromIndices, token) {
873
- // If we have library map, always use the map for library symbols.
874
- const predicate = (s) => {
875
- if (!libraryMap) {
876
- // We don't have any prebuilt indices, so we need to include
877
- // all files.
878
- return true;
879
- }
880
- if (!this._configOptions.indexing) {
881
- // We have some prebuilt indices such as stdlib, but indexing is disabled.
882
- // Include files we don't have prebuilt indices.
883
- return libraryMap.get(s.sourceFile.getFilePath()) === undefined;
932
+ clone() {
933
+ var _a, _b;
934
+ const program = new Program(this._importResolver, this._configOptions, this._console, new logTracker_1.LogTracker(this._console, 'Cloned'));
935
+ // Cloned program will use whatever user files the program currently has.
936
+ const userFiles = this.getUserFiles();
937
+ program.setTrackedFiles(userFiles.map((i) => i.sourceFile.getFilePath()));
938
+ program.markAllFilesDirty(true);
939
+ // Make sure we keep editor content (open file) which could be different than one in the file system.
940
+ for (const fileInfo of this.getOpened()) {
941
+ const version = fileInfo.sourceFile.getClientVersion();
942
+ if (version === undefined) {
943
+ continue;
884
944
  }
885
- // We have prebuilt indices for third party libraries. Include only
886
- // user files.
887
- return (0, sourceFileInfoUtils_1.isUserCode)(s);
888
- };
889
- // Only include import alias from user files if indexing is off for now.
890
- // Currently, when indexing is off, we don't do import alias deduplication, so
891
- // adding import alias is cheap. But when indexing is on, we do deduplication, which
892
- // require resolveAliasDeclaration that can cause more files to be parsed and bound.
893
- return (0, autoImporter_1.buildModuleSymbolsMap)(this._sourceFileList.filter((s) => s !== sourceFileToExclude && predicate(s)), includeSymbolsFromIndices, token);
945
+ program.setFileOpened(fileInfo.sourceFile.getFilePath(), version, (_a = fileInfo.sourceFile.getOpenFileContents()) !== null && _a !== void 0 ? _a : '', {
946
+ chainedFilePath: (_b = fileInfo.chainedSourceFile) === null || _b === void 0 ? void 0 : _b.sourceFile.getFilePath(),
947
+ ipythonMode: fileInfo.sourceFile.getIPythonMode(),
948
+ isTracked: fileInfo.isTracked,
949
+ realFilePath: fileInfo.sourceFile.getRealFilePath(),
950
+ });
951
+ }
952
+ return program;
894
953
  }
895
- _shouldCheckFile(fileInfo) {
896
- // Always do a full checking for a file that's open in the editor.
897
- if (fileInfo.isOpenByClient) {
898
- return true;
954
+ // Returns a value from 0 to 1 (or more) indicating how "full" the cache is
955
+ // relative to some predetermined high-water mark. We'll compute this value
956
+ // based on two easy-to-compute metrics: the number of entries in the type
957
+ // cache and the number of parsed files.
958
+ getCacheUsage() {
959
+ const typeCacheEntryCount = this._evaluator.getTypeCacheEntryCount();
960
+ const entryCountRatio = typeCacheEntryCount / 750000;
961
+ const fileCountRatio = this._parsedFileCount / 1000;
962
+ return Math.max(entryCountRatio, fileCountRatio);
963
+ }
964
+ // Discards any cached information associated with this program.
965
+ emptyCache() {
966
+ this._createNewEvaluator();
967
+ this._discardCachedParseResults();
968
+ this._parsedFileCount = 0;
969
+ extensibility_1.Extensions.getProgramExtensions(this.rootPath).forEach((e) => (e.clearCache ? e.clearCache() : null));
970
+ }
971
+ test_createSourceMapper(execEnv, from) {
972
+ return this._createSourceMapper(execEnv, vscode_languageserver_1.CancellationToken.None, /* from */ from, /* mapCompiled */ false);
973
+ }
974
+ _handleMemoryHighUsage() {
975
+ const cacheUsage = this._cacheManager.getCacheUsage();
976
+ // If the total cache has exceeded 75%, determine whether we should empty
977
+ // the cache.
978
+ if (cacheUsage > 0.75) {
979
+ const usedHeapRatio = this._cacheManager.getUsedHeapRatio(this._configOptions.verboseOutput ? this._console : undefined);
980
+ // The type cache uses a Map, which has an absolute limit of 2^24 entries
981
+ // before it will fail. If we cross the 95% mark, we'll empty the cache.
982
+ const absoluteMaxCacheEntryCount = (1 << 24) * 0.9;
983
+ const typeCacheEntryCount = this._evaluator.getTypeCacheEntryCount();
984
+ // If we use more than 90% of the heap size limit, avoid a crash
985
+ // by emptying the type cache.
986
+ if (typeCacheEntryCount > absoluteMaxCacheEntryCount || usedHeapRatio > 0.9) {
987
+ this._cacheManager.emptyCache(this._console);
988
+ }
899
989
  }
900
- // If the file isn't currently open, only perform full checking for
901
- // files that are tracked, and only if the checkOnlyOpenFiles is disabled.
902
- if (!this._configOptions.checkOnlyOpenFiles && fileInfo.isTracked) {
903
- return true;
990
+ }
991
+ // Discards all cached parse results and file contents to free up memory.
992
+ // It does not discard cached index results or diagnostics for files.
993
+ _discardCachedParseResults() {
994
+ for (const sourceFileInfo of this._sourceFileList) {
995
+ sourceFileInfo.sourceFile.dropParseAndBindInfo();
904
996
  }
905
- return false;
906
997
  }
907
- _checkTypes(fileToCheck, token) {
908
- return this._logTracker.log(`analyzing: ${fileToCheck.sourceFile.getFilePath()}`, (logState) => {
909
- // If the file isn't needed because it was eliminated from the
910
- // transitive closure or deleted, skip the file rather than wasting
911
- // time on it.
912
- if (!this._isFileNeeded(fileToCheck)) {
913
- logState.suppress();
914
- return false;
998
+ // Wrapper function that should be used when invoking this._evaluator
999
+ // with a cancellation token. It handles cancellation exceptions and
1000
+ // any other unexpected exceptions.
1001
+ _runEvaluatorWithCancellationToken(token, callback) {
1002
+ try {
1003
+ if (token) {
1004
+ return this._evaluator.runWithCancellationToken(token, callback);
915
1005
  }
916
- if (!fileToCheck.sourceFile.isCheckingRequired()) {
917
- logState.suppress();
918
- return false;
1006
+ else {
1007
+ return callback();
919
1008
  }
920
- if (!this._shouldCheckFile(fileToCheck)) {
921
- logState.suppress();
922
- return false;
1009
+ }
1010
+ catch (e) {
1011
+ // An unexpected exception occurred, potentially leaving the current evaluator
1012
+ // in an inconsistent state. Discard it and replace it with a fresh one. It is
1013
+ // Cancellation exceptions are known to handle this correctly.
1014
+ if (!cancellationUtils_1.OperationCanceledException.is(e)) {
1015
+ this._createNewEvaluator();
923
1016
  }
924
- this._bindFile(fileToCheck);
925
- if (this._preCheckCallback) {
926
- const parseResults = fileToCheck.sourceFile.getParseResults();
927
- if (parseResults) {
928
- this._preCheckCallback(parseResults, this._evaluator);
929
- }
930
- }
931
- if (!this._disableChecker) {
932
- // For ipython, make sure we check all its dependent files first since
933
- // their results can affect this file's result.
934
- let dependentFiles = undefined;
935
- if (fileToCheck.sourceFile.getIPythonMode() === sourceFile_1.IPythonMode.CellDocs) {
936
- dependentFiles = [];
937
- const importedByFiles = (0, sourceFileInfoUtils_1.collectImportedByFiles)(fileToCheck);
938
- for (const file of importedByFiles) {
939
- if (!(0, sourceFileInfoUtils_1.isUserCode)(file)) {
940
- continue;
941
- }
942
- // If the file is already analyzed, it will be no op.
943
- // And make sure we don't dump parse tree and etc while
944
- // recursively calling checker. Otherwise, inner check
945
- // can dump parse tree required by outer check.
946
- const handle = this._cacheManager.pauseTracking();
947
- try {
948
- this._checkTypes(file, token);
949
- }
950
- finally {
951
- handle.dispose();
952
- }
953
- const parseResults = file.sourceFile.getParseResults();
954
- if (parseResults) {
955
- dependentFiles.push(parseResults);
1017
+ throw e;
1018
+ }
1019
+ }
1020
+ // Returns a list of empty file diagnostic entries for the files
1021
+ // that have been removed. This is needed to clear out the
1022
+ // errors for files that have been deleted or closed.
1023
+ _removeUnneededFiles() {
1024
+ const fileDiagnostics = [];
1025
+ // If a file is no longer tracked, opened or shadowed, it can
1026
+ // be removed from the program.
1027
+ for (let i = 0; i < this._sourceFileList.length;) {
1028
+ const fileInfo = this._sourceFileList[i];
1029
+ if (!this._isFileNeeded(fileInfo)) {
1030
+ fileDiagnostics.push({
1031
+ filePath: fileInfo.sourceFile.getFilePath(),
1032
+ version: fileInfo.sourceFile.getClientVersion(),
1033
+ diagnostics: [],
1034
+ });
1035
+ fileInfo.sourceFile.prepareForClose();
1036
+ this._removeSourceFileFromListAndMap(fileInfo.sourceFile.getFilePath(), i);
1037
+ // Unlink any imports and remove them from the list if
1038
+ // they are no longer referenced.
1039
+ fileInfo.imports.forEach((importedFile) => {
1040
+ const indexToRemove = importedFile.importedBy.findIndex((fi) => fi === fileInfo);
1041
+ if (indexToRemove < 0) {
1042
+ return;
1043
+ }
1044
+ importedFile.importedBy.splice(indexToRemove, 1);
1045
+ // See if we need to remove the imported file because it
1046
+ // is no longer needed. If its index is >= i, it will be
1047
+ // removed when we get to it.
1048
+ if (!this._isFileNeeded(importedFile)) {
1049
+ const indexToRemove = this._sourceFileList.findIndex((fi) => fi === importedFile);
1050
+ if (indexToRemove >= 0 && indexToRemove < i) {
1051
+ fileDiagnostics.push({
1052
+ filePath: importedFile.sourceFile.getFilePath(),
1053
+ version: importedFile.sourceFile.getClientVersion(),
1054
+ diagnostics: [],
1055
+ });
1056
+ importedFile.sourceFile.prepareForClose();
1057
+ this._removeSourceFileFromListAndMap(importedFile.sourceFile.getFilePath(), indexToRemove);
1058
+ i--;
956
1059
  }
957
1060
  }
958
- }
959
- const execEnv = this._configOptions.findExecEnvironment(fileToCheck.sourceFile.getFilePath());
960
- fileToCheck.sourceFile.check(this._importResolver, this._evaluator, this._createSourceMapper(execEnv, token, fileToCheck), dependentFiles);
1061
+ });
1062
+ // Remove any shadowed files corresponding to this file.
1063
+ fileInfo.shadowedBy.forEach((shadowedFile) => {
1064
+ shadowedFile.shadows = shadowedFile.shadows.filter((f) => f !== fileInfo);
1065
+ });
1066
+ fileInfo.shadowedBy = [];
961
1067
  }
962
- // For very large programs, we may need to discard the evaluator and
963
- // its cached types to avoid running out of heap space.
964
- this._handleMemoryHighUsage();
965
- // Detect import cycles that involve the file.
966
- if (this._configOptions.diagnosticRuleSet.reportImportCycles !== 'none') {
967
- // Don't detect import cycles when doing type stub generation. Some
968
- // third-party modules are pretty convoluted.
969
- if (!this._allowedThirdPartyImports) {
970
- // We need to force all of the files to be parsed and build
971
- // a closure map for the files.
972
- const closureMap = new Map();
973
- this._getImportsRecursive(fileToCheck, closureMap, 0);
974
- closureMap.forEach((file) => {
975
- timing_1.timingStats.cycleDetectionTime.timeOperation(() => {
976
- const filesVisitedMap = new Map();
977
- if (!this._detectAndReportImportCycles(file, filesVisitedMap)) {
978
- // If no cycles were found in any of the files we visited,
979
- // set a flag to indicates that we don't need to visit them again
980
- // on subsequent cycle checks.
981
- filesVisitedMap.forEach((sourceFileInfo) => {
982
- sourceFileInfo.sourceFile.setNoCircularDependencyConfirmed();
983
- });
984
- }
985
- });
1068
+ else {
1069
+ // If we're showing the user errors only for open files, clear
1070
+ // out the errors for the now-closed file.
1071
+ if (!this._shouldCheckFile(fileInfo) && fileInfo.diagnosticsVersion !== undefined) {
1072
+ fileDiagnostics.push({
1073
+ filePath: fileInfo.sourceFile.getFilePath(),
1074
+ version: fileInfo.sourceFile.getClientVersion(),
1075
+ diagnostics: [],
986
1076
  });
1077
+ fileInfo.diagnosticsVersion = undefined;
987
1078
  }
1079
+ i++;
988
1080
  }
989
- return true;
990
- });
1081
+ }
1082
+ return fileDiagnostics;
991
1083
  }
992
- // Builds a map of files that includes the specified file and all of the files
993
- // it imports (recursively) and ensures that all such files. If any of these files
994
- // have already been checked (they and their recursive imports have completed the
995
- // check phase), they are not included in the results.
996
- _getImportsRecursive(file, closureMap, recursionCount) {
997
- // If the file is already in the closure map, we found a cyclical
998
- // dependency. Don't recur further.
999
- const filePath = (0, pathUtils_1.normalizePathCase)(this._fs, file.sourceFile.getFilePath());
1000
- if (closureMap.has(filePath)) {
1001
- return;
1084
+ _isFileNeeded(fileInfo, skipFileNeededCheck) {
1085
+ if (fileInfo.sourceFile.isFileDeleted()) {
1086
+ return false;
1002
1087
  }
1003
- // If the import chain is too long, emit an error. Otherwise we
1004
- // risk blowing the stack.
1005
- if (recursionCount > _maxImportDepth) {
1006
- file.sourceFile.setHitMaxImportDepth(_maxImportDepth);
1007
- return;
1088
+ if (!!skipFileNeededCheck || fileInfo.isTracked || fileInfo.isOpenByClient) {
1089
+ return true;
1008
1090
  }
1009
- // Add the file to the closure map.
1010
- closureMap.set(filePath, file);
1011
- // If this file hasn't already been parsed, parse it now. This will
1012
- // discover any files it imports. Skip this if the file is part
1013
- // of a library. We'll assume that no cycles will be generated from
1014
- // library code or typeshed stubs.
1015
- if ((0, sourceFileInfoUtils_1.isUserCode)(file)) {
1016
- this._parseFile(file);
1091
+ if (fileInfo.shadows.length > 0) {
1092
+ return true;
1017
1093
  }
1018
- // Recursively add the file's imports.
1019
- for (const importedFileInfo of file.imports) {
1020
- this._getImportsRecursive(importedFileInfo, closureMap, recursionCount + 1);
1094
+ if (fileInfo.importedBy.length === 0) {
1095
+ return false;
1021
1096
  }
1097
+ // It's possible for a cycle of files to be imported
1098
+ // by a tracked file but then abandoned. The import cycle
1099
+ // will keep the entire group "alive" if we don't detect
1100
+ // the condition and garbage collect them.
1101
+ return this._isImportNeededRecursive(fileInfo, new Set());
1022
1102
  }
1023
- _detectAndReportImportCycles(sourceFileInfo, filesVisited, dependencyChain = [], dependencyMap = new Map()) {
1024
- // Don't bother checking for typestub files or third-party files.
1025
- if (sourceFileInfo.sourceFile.isStubFile() || sourceFileInfo.isThirdPartyImport) {
1026
- return false;
1103
+ _isImportNeededRecursive(fileInfo, recursionSet) {
1104
+ if (fileInfo.isTracked || fileInfo.isOpenByClient || fileInfo.shadows.length > 0) {
1105
+ return true;
1027
1106
  }
1028
- // If we've already confirmed that this source file isn't part of a
1029
- // cycle, we can skip it entirely.
1030
- if (sourceFileInfo.sourceFile.isNoCircularDependencyConfirmed()) {
1107
+ const filePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, fileInfo.sourceFile.getFilePath());
1108
+ // Avoid infinite recursion.
1109
+ if (recursionSet.has(filePath)) {
1031
1110
  return false;
1032
1111
  }
1033
- const filePath = (0, pathUtils_1.normalizePathCase)(this._fs, sourceFileInfo.sourceFile.getFilePath());
1034
- filesVisited.set(filePath, sourceFileInfo);
1035
- let detectedCycle = false;
1036
- if (dependencyMap.has(filePath)) {
1037
- // We detect a cycle (partial or full). A full cycle is one that is
1038
- // rooted in the file at the start of our dependency chain. A partial
1039
- // cycle loops back on some other file in the dependency chain. We
1040
- // will report only full cycles here and leave the reporting of
1041
- // partial cycles to other passes.
1042
- detectedCycle = true;
1043
- // Look for chains at least two in length. A file that contains
1044
- // an "import . from X" will technically create a cycle with
1045
- // itself, but those are not interesting to report.
1046
- if (dependencyChain.length > 1 && sourceFileInfo === dependencyChain[0]) {
1047
- this._logImportCycle(dependencyChain);
1112
+ recursionSet.add(filePath);
1113
+ for (const importerInfo of fileInfo.importedBy) {
1114
+ if (this._isImportNeededRecursive(importerInfo, recursionSet)) {
1115
+ return true;
1048
1116
  }
1049
1117
  }
1050
- else {
1051
- // If we've already checked this dependency along
1052
- // some other path, we can skip it.
1053
- if (dependencyMap.has(filePath)) {
1054
- return false;
1118
+ return false;
1119
+ }
1120
+ _createSourceMapper(execEnv, token, from, mapCompiled, preferStubs) {
1121
+ const sourceMapper = new sourceMapper_1.SourceMapper(this._importResolver, execEnv, this._evaluator, (stubFilePath, implFilePath) => {
1122
+ let stubFileInfo = this.getSourceFileInfo(stubFilePath);
1123
+ if (!stubFileInfo) {
1124
+ // Special case for import statement like "import X.Y". The SourceFile
1125
+ // for X might not be in memory since import `X.Y` only brings in Y.
1126
+ stubFileInfo = this.addInterimFile(stubFilePath);
1055
1127
  }
1056
- // We use both a map (for fast lookups) and a list
1057
- // (for ordering information). Set the dependency map
1058
- // entry to true to indicate that we're actively exploring
1059
- // that dependency.
1060
- dependencyMap.set(filePath, true);
1061
- dependencyChain.push(sourceFileInfo);
1062
- for (const imp of sourceFileInfo.imports) {
1063
- if (this._detectAndReportImportCycles(imp, filesVisited, dependencyChain, dependencyMap)) {
1064
- detectedCycle = true;
1065
- }
1128
+ this._addShadowedFile(stubFileInfo, implFilePath);
1129
+ return this.getBoundSourceFile(implFilePath);
1130
+ }, (f) => {
1131
+ let fileInfo = this.getBoundSourceFileInfo(f);
1132
+ if (!fileInfo) {
1133
+ // Special case for import statement like "import X.Y". The SourceFile
1134
+ // for X might not be in memory since import `X.Y` only brings in Y.
1135
+ fileInfo = this.addInterimFile(f);
1136
+ // Even though this file is not referenced by anything, make sure
1137
+ // we have a parse tree for the doc string.
1138
+ this._parseFile(fileInfo, /* content */ undefined, /* force */ true);
1066
1139
  }
1067
- // Set the dependencyMap entry to false to indicate that we have
1068
- // already explored this file and don't need to explore it again.
1069
- dependencyMap.set(filePath, false);
1070
- dependencyChain.pop();
1071
- }
1072
- return detectedCycle;
1073
- }
1074
- _logImportCycle(dependencyChain) {
1075
- const circDep = new circularDependency_1.CircularDependency();
1076
- dependencyChain.forEach((sourceFileInfo) => {
1077
- circDep.appendPath(sourceFileInfo.sourceFile.getFilePath());
1078
- });
1079
- circDep.normalizeOrder();
1080
- const firstFilePath = circDep.getPaths()[0];
1081
- const firstSourceFile = this.getSourceFileInfo(firstFilePath);
1082
- (0, debug_1.assert)(firstSourceFile !== undefined);
1083
- firstSourceFile.sourceFile.addCircularDependency(circDep);
1140
+ return fileInfo;
1141
+ }, mapCompiled !== null && mapCompiled !== void 0 ? mapCompiled : false, preferStubs !== null && preferStubs !== void 0 ? preferStubs : false, from, token);
1142
+ return sourceMapper;
1084
1143
  }
1085
- _markFileDirtyRecursive(sourceFileInfo, markSet, forceRebinding = false) {
1086
- const filePath = (0, pathUtils_1.normalizePathCase)(this._fs, sourceFileInfo.sourceFile.getFilePath());
1087
- // Don't mark it again if it's already been visited.
1088
- if (markSet.has(filePath)) {
1089
- return;
1144
+ _isImportAllowed(importer, importResult, isImportStubFile) {
1145
+ // Don't import native libs. We don't want to track these files,
1146
+ // and we definitely don't want to attempt to parse them.
1147
+ if (importResult.isNativeLib) {
1148
+ return false;
1090
1149
  }
1091
- sourceFileInfo.sourceFile.markReanalysisRequired(forceRebinding);
1092
- markSet.add(filePath);
1093
- sourceFileInfo.importedBy.forEach((dep) => {
1094
- // Changes on chained source file can change symbols in the symbol table and
1095
- // dependencies on the dependent file. Force rebinding.
1096
- const forceRebinding = dep.chainedSourceFile === sourceFileInfo;
1097
- this._markFileDirtyRecursive(dep, markSet, forceRebinding);
1098
- });
1099
- // Change in the current file could impact checker result of chainedSourceFile such as unused symbols.
1100
- let chainedSourceFile = sourceFileInfo.chainedSourceFile;
1101
- while (chainedSourceFile) {
1102
- if (chainedSourceFile.sourceFile.isCheckingRequired()) {
1103
- // If the file is marked for checking, its chained one should be marked
1104
- // as well. Stop here.
1105
- return;
1106
- }
1107
- chainedSourceFile.sourceFile.markReanalysisRequired(/* forceRebinding */ false);
1108
- chainedSourceFile = chainedSourceFile.chainedSourceFile;
1109
- }
1110
- }
1111
- getTextOnRange(filePath, range, token) {
1112
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1113
- if (!sourceFileInfo) {
1114
- return undefined;
1115
- }
1116
- const sourceFile = sourceFileInfo.sourceFile;
1117
- const fileContents = sourceFile.getOpenFileContents();
1118
- if (fileContents === undefined) {
1119
- // this only works with opened file
1120
- return undefined;
1121
- }
1122
- return this._runEvaluatorWithCancellationToken(token, () => {
1123
- this._parseFile(sourceFileInfo);
1124
- const parseTree = sourceFile.getParseResults();
1125
- const textRange = (0, positionUtils_1.convertRangeToTextRange)(range, parseTree.tokenizerOutput.lines);
1126
- if (!textRange) {
1127
- return undefined;
1128
- }
1129
- return fileContents.substr(textRange.start, textRange.length);
1130
- });
1131
- }
1132
- getAutoImports(filePath, range, similarityLimit, nameMap, options, token) {
1133
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1134
- if (!sourceFileInfo) {
1135
- return [];
1136
- }
1137
- const sourceFile = sourceFileInfo.sourceFile;
1138
- const fileContents = sourceFile.getOpenFileContents();
1139
- if (fileContents === undefined) {
1140
- // this only works with opened file
1141
- return [];
1142
- }
1143
- return this._runEvaluatorWithCancellationToken(token, () => {
1144
- var _a;
1145
- this._bindFile(sourceFileInfo);
1146
- const parseTree = sourceFile.getParseResults();
1147
- const textRange = (0, positionUtils_1.convertRangeToTextRange)(range, parseTree.tokenizerOutput.lines);
1148
- if (!textRange) {
1149
- return [];
1150
- }
1151
- const currentNode = (0, parseTreeUtils_1.findNodeByOffset)(parseTree.parseTree, textRange.start);
1152
- if (!currentNode) {
1153
- return [];
1154
- }
1155
- const writtenWord = fileContents.substr(textRange.start, textRange.length);
1156
- const map = this._buildModuleSymbolsMap(sourceFileInfo, options.libraryMap,
1157
- /* includeSymbolsFromIndices */ true, token);
1158
- options.patternMatcher =
1159
- (_a = options.patternMatcher) !== null && _a !== void 0 ? _a : ((p, t) => (0, stringUtils_1.computeCompletionSimilarity)(p, t) > similarityLimit);
1160
- const autoImporter = new autoImporter_1.AutoImporter(this._configOptions.findExecEnvironment(filePath), this._importResolver, parseTree, range.start, new completionProvider_1.CompletionMap(), map, options);
1161
- // Filter out any name that is already defined in the current scope.
1162
- const results = [];
1163
- const currentScope = (0, scopeUtils_1.getScopeForNode)(currentNode);
1164
- if (currentScope) {
1165
- const info = nameMap === null || nameMap === void 0 ? void 0 : nameMap.get(writtenWord);
1166
- if (info) {
1167
- // No scope filter is needed since we only do exact match.
1168
- (0, collectionUtils_1.appendArray)(results, autoImporter.getAutoImportCandidatesForAbbr(writtenWord, info, token));
1169
- }
1170
- results.push(...autoImporter
1171
- .getAutoImportCandidates(writtenWord, similarityLimit, /* abbrFromUsers */ undefined, token)
1172
- .filter((r) => !currentScope.lookUpSymbolRecursive(r.name)));
1173
- }
1174
- return results;
1175
- });
1176
- }
1177
- getDiagnostics(options) {
1178
- const fileDiagnostics = this._removeUnneededFiles();
1179
- this._sourceFileList.forEach((sourceFileInfo) => {
1180
- if (this._shouldCheckFile(sourceFileInfo)) {
1181
- const diagnostics = sourceFileInfo.sourceFile.getDiagnostics(options, sourceFileInfo.diagnosticsVersion);
1182
- if (diagnostics !== undefined) {
1183
- fileDiagnostics.push({
1184
- filePath: sourceFileInfo.sourceFile.getFilePath(),
1185
- version: sourceFileInfo.sourceFile.getClientVersion(),
1186
- diagnostics,
1187
- });
1188
- // Update the cached diagnosticsVersion so we can determine
1189
- // whether there are any updates next time we call getDiagnostics.
1190
- sourceFileInfo.diagnosticsVersion = sourceFileInfo.sourceFile.getDiagnosticVersion();
1191
- }
1192
- }
1193
- else if (!sourceFileInfo.isOpenByClient &&
1194
- options.checkOnlyOpenFiles &&
1195
- sourceFileInfo.diagnosticsVersion !== undefined) {
1196
- // This condition occurs when the user switches from workspace to
1197
- // "open files only" mode. Clear all diagnostics for this file.
1198
- fileDiagnostics.push({
1199
- filePath: sourceFileInfo.sourceFile.getFilePath(),
1200
- version: sourceFileInfo.sourceFile.getClientVersion(),
1201
- diagnostics: [],
1202
- });
1203
- sourceFileInfo.diagnosticsVersion = undefined;
1204
- }
1205
- });
1206
- return fileDiagnostics;
1207
- }
1208
- getDiagnosticsForRange(filePath, range) {
1209
- const sourceFile = this.getSourceFile(filePath);
1210
- if (!sourceFile) {
1211
- return [];
1212
- }
1213
- const unfilteredDiagnostics = sourceFile.getDiagnostics(this._configOptions);
1214
- if (!unfilteredDiagnostics) {
1215
- return [];
1216
- }
1217
- return unfilteredDiagnostics.filter((diag) => {
1218
- return (0, textRange_1.doRangesIntersect)(diag.range, range);
1219
- });
1220
- }
1221
- getDefinitionsForPosition(filePath, position, filter, token) {
1222
- return this._runEvaluatorWithCancellationToken(token, () => {
1223
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1224
- if (!sourceFileInfo) {
1225
- return undefined;
1226
- }
1227
- this._bindFile(sourceFileInfo);
1228
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1229
- return sourceFileInfo.sourceFile.getDefinitionsForPosition(this._createSourceMapper(execEnv, token, sourceFileInfo), position, filter, this._evaluator, token);
1230
- });
1231
- }
1232
- getTypeDefinitionsForPosition(filePath, position, token) {
1233
- return this._runEvaluatorWithCancellationToken(token, () => {
1234
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1235
- if (!sourceFileInfo) {
1236
- return undefined;
1237
- }
1238
- this._bindFile(sourceFileInfo);
1239
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1240
- return sourceFileInfo.sourceFile.getTypeDefinitionsForPosition(this._createSourceMapper(execEnv, token, sourceFileInfo,
1241
- /* mapCompiled */ false,
1242
- /* preferStubs */ true), position, this._evaluator, filePath, token);
1243
- });
1244
- }
1245
- reportReferencesForPosition(filePath, position, includeDeclaration, reporter, token) {
1246
- this._runEvaluatorWithCancellationToken(token, () => {
1247
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1248
- if (!sourceFileInfo) {
1249
- return;
1250
- }
1251
- const invokedFromUserFile = (0, sourceFileInfoUtils_1.isUserCode)(sourceFileInfo);
1252
- this._bindFile(sourceFileInfo);
1253
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1254
- const referencesResult = this._getDeclarationForPosition(sourceFileInfo, position, documentSymbolCollector_1.DocumentSymbolCollectorUseCase.Reference, this._createSourceMapper(execEnv, token, sourceFileInfo), token, reporter);
1255
- if (!referencesResult) {
1256
- return;
1257
- }
1258
- // Do we need to do a global search as well?
1259
- if (referencesResult.requiresGlobalSearch) {
1260
- for (const curSourceFileInfo of this._sourceFileList) {
1261
- (0, cancellationUtils_1.throwIfCancellationRequested)(token);
1262
- // "Find all references" will only include references from user code
1263
- // unless the file is explicitly opened in the editor or it is invoked from non user files.
1264
- if (curSourceFileInfo.isOpenByClient || !invokedFromUserFile || (0, sourceFileInfoUtils_1.isUserCode)(curSourceFileInfo)) {
1265
- // See if the reference symbol's string is located somewhere within the file.
1266
- // If not, we can skip additional processing for the file.
1267
- const fileContents = curSourceFileInfo.sourceFile.getFileContent();
1268
- if (!fileContents || referencesResult.symbolNames.some((s) => fileContents.search(s) >= 0)) {
1269
- this._bindFile(curSourceFileInfo);
1270
- curSourceFileInfo.sourceFile.addReferences(referencesResult, includeDeclaration, this._evaluator, token);
1271
- }
1272
- // This operation can consume significant memory, so check
1273
- // for situations where we need to discard the type cache.
1274
- this._handleMemoryHighUsage();
1275
- }
1276
- }
1277
- // Make sure to include declarations regardless where they are defined
1278
- // if includeDeclaration is set.
1279
- if (includeDeclaration) {
1280
- for (const decl of referencesResult.declarations) {
1281
- (0, cancellationUtils_1.throwIfCancellationRequested)(token);
1282
- if (referencesResult.locations.some((l) => l.path === decl.path)) {
1283
- // Already included.
1284
- continue;
1285
- }
1286
- const declFileInfo = this.getSourceFileInfo(decl.path);
1287
- if (!declFileInfo) {
1288
- // The file the declaration belongs to doesn't belong to the program.
1289
- continue;
1290
- }
1291
- const tempResult = new referencesProvider_1.ReferencesResult(referencesResult.requiresGlobalSearch, referencesResult.nodeAtOffset, referencesResult.symbolNames, referencesResult.declarations, referencesResult.useCase);
1292
- declFileInfo.sourceFile.addReferences(tempResult, includeDeclaration, this._evaluator, token);
1293
- for (const loc of tempResult.locations) {
1294
- // Include declarations only. And throw away any references
1295
- if (loc.path === decl.path && (0, textRange_1.doesRangeContain)(decl.range, loc.range)) {
1296
- referencesResult.addLocations(loc);
1297
- }
1298
- }
1299
- }
1300
- }
1301
- }
1302
- else {
1303
- sourceFileInfo.sourceFile.addReferences(referencesResult, includeDeclaration, this._evaluator, token);
1304
- }
1305
- });
1306
- }
1307
- getFileIndex(filePath, options, token) {
1308
- if (options.indexingForAutoImportMode) {
1309
- // Memory optimization. We only want to hold onto symbols
1310
- // usable outside when importSymbolsOnly is on.
1311
- const name = (0, pathUtils_1.stripFileExtension)((0, pathUtils_1.getFileName)(filePath));
1312
- if ((0, symbolNameUtils_1.isPrivateOrProtectedName)(name)) {
1313
- return undefined;
1314
- }
1315
- }
1316
- this._handleMemoryHighUsage();
1317
- return this._runEvaluatorWithCancellationToken(token, () => {
1318
- var _a;
1319
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1320
- if (!sourceFileInfo) {
1321
- return undefined;
1322
- }
1323
- const content = (_a = sourceFileInfo.sourceFile.getFileContent()) !== null && _a !== void 0 ? _a : '';
1324
- if (options.indexingForAutoImportMode &&
1325
- !options.includeAllSymbols &&
1326
- !sourceFileInfo.sourceFile.isStubFile() &&
1327
- !sourceFileInfo.sourceFile.isThirdPartyPyTypedPresent()) {
1328
- // Perf optimization. if py file doesn't contain __all__
1329
- // No need to parse and bind.
1330
- if (content.indexOf('__all__') < 0) {
1331
- return undefined;
1332
- }
1333
- }
1334
- this._bindFile(sourceFileInfo, content);
1335
- return sourceFileInfo.sourceFile.index(options, token);
1336
- });
1337
- }
1338
- addSymbolsForDocument(filePath, symbolList, token) {
1339
- return this._runEvaluatorWithCancellationToken(token, () => {
1340
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1341
- if (sourceFileInfo) {
1342
- if (!sourceFileInfo.sourceFile.getCachedIndexResults()) {
1343
- // If we already have cached index for this file, no need to bind this file.
1344
- this._bindFile(sourceFileInfo);
1345
- }
1346
- sourceFileInfo.sourceFile.addHierarchicalSymbolsForDocument(symbolList, token);
1347
- }
1348
- });
1349
- }
1350
- reportSymbolsForWorkspace(query, reporter, token) {
1351
- this._runEvaluatorWithCancellationToken(token, () => {
1352
- // Don't do a search if the query is empty. We'll return
1353
- // too many results in this case.
1354
- if (!query) {
1355
- return;
1356
- }
1357
- // "Workspace symbols" searches symbols only from user code.
1358
- for (const sourceFileInfo of this._sourceFileList) {
1359
- if (!(0, sourceFileInfoUtils_1.isUserCode)(sourceFileInfo)) {
1360
- continue;
1361
- }
1362
- if (!sourceFileInfo.sourceFile.getCachedIndexResults()) {
1363
- // If we already have cached index for this file, no need to bind this file.
1364
- this._bindFile(sourceFileInfo);
1365
- }
1366
- const symbolList = sourceFileInfo.sourceFile.getSymbolsForDocument(query, token);
1367
- if (symbolList.length > 0) {
1368
- reporter(symbolList);
1369
- }
1370
- // This operation can consume significant memory, so check
1371
- // for situations where we need to discard the type cache.
1372
- this._handleMemoryHighUsage();
1373
- }
1374
- });
1375
- }
1376
- getDocumentHighlight(filePath, position, token) {
1377
- return this._runEvaluatorWithCancellationToken(token, () => {
1378
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1379
- if (!sourceFileInfo) {
1380
- return undefined;
1381
- }
1382
- this._bindFile(sourceFileInfo);
1383
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1384
- return sourceFileInfo.sourceFile.getDocumentHighlight(this._createSourceMapper(execEnv, token, sourceFileInfo), position, this._evaluator, token);
1385
- });
1386
- }
1387
- getSignatureHelpForPosition(filePath, position, format, token) {
1388
- return this._runEvaluatorWithCancellationToken(token, () => {
1389
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1390
- if (!sourceFileInfo) {
1391
- return undefined;
1392
- }
1393
- this._bindFile(sourceFileInfo);
1394
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1395
- return sourceFileInfo.sourceFile.getSignatureHelpForPosition(position, this._createSourceMapper(execEnv, token, sourceFileInfo, /* mapCompiled */ true), this._evaluator, format, token);
1396
- });
1397
- }
1398
- async getCompletionsForPosition(filePath, position, workspacePath, options, nameMap, libraryMap, token) {
1399
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1400
- if (!sourceFileInfo) {
1401
- return undefined;
1402
- }
1403
- let sourceMapper;
1404
- const completionResult = this._logTracker.log(`completion at ${filePath}:${position.line}:${position.character}`, (ls) => {
1405
- var _a;
1406
- const result = this._runEvaluatorWithCancellationToken(token, () => {
1407
- this._bindFile(sourceFileInfo);
1408
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1409
- sourceMapper = this._createSourceMapper(execEnv, token, sourceFileInfo, /* mapCompiled */ true);
1410
- return sourceFileInfo.sourceFile.getCompletionsForPosition(position, workspacePath, this._configOptions, this._importResolver, this._lookUpImport, this._evaluator, options, sourceMapper, nameMap, libraryMap, () => this._buildModuleSymbolsMap(sourceFileInfo, libraryMap, options.includeUserSymbolsInAutoImport, token), token);
1411
- });
1412
- ls.add(`found ${(_a = result === null || result === void 0 ? void 0 : result.completionMap.size) !== null && _a !== void 0 ? _a : 'null'} items`);
1413
- return result;
1414
- });
1415
- const completionResultsList = {
1416
- completionList: vscode_languageserver_types_1.CompletionList.create(completionResult === null || completionResult === void 0 ? void 0 : completionResult.completionMap.toArray()),
1417
- memberAccessInfo: completionResult === null || completionResult === void 0 ? void 0 : completionResult.memberAccessInfo,
1418
- autoImportInfo: completionResult === null || completionResult === void 0 ? void 0 : completionResult.autoImportInfo,
1419
- extensionInfo: completionResult === null || completionResult === void 0 ? void 0 : completionResult.extensionInfo,
1420
- };
1421
- const parseResults = sourceFileInfo.sourceFile.getParseResults();
1422
- if ((parseResults === null || parseResults === void 0 ? void 0 : parseResults.parseTree) && (parseResults === null || parseResults === void 0 ? void 0 : parseResults.text)) {
1423
- const offset = (0, positionUtils_1.convertPositionToOffset)(position, parseResults.tokenizerOutput.lines);
1424
- if (offset !== undefined && sourceMapper) {
1425
- await Promise.all(extensibility_1.Extensions.getProgramExtensions(parseResults.parseTree).map((e) => {
1426
- var _a;
1427
- return (_a = e.completionListExtension) === null || _a === void 0 ? void 0 : _a.updateCompletionResults(this.evaluator, sourceMapper, options, completionResultsList, parseResults, offset, this._configOptions.functionSignatureDisplay, token);
1428
- }));
1429
- }
1430
- }
1431
- return completionResultsList;
1432
- }
1433
- resolveCompletionItem(filePath, completionItem, options, nameMap, libraryMap, token) {
1434
- return this._runEvaluatorWithCancellationToken(token, () => {
1435
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1436
- if (!sourceFileInfo) {
1437
- return;
1438
- }
1439
- this._bindFile(sourceFileInfo);
1440
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1441
- sourceFileInfo.sourceFile.resolveCompletionItem(this._configOptions, this._importResolver, this._lookUpImport, this._evaluator, options, this._createSourceMapper(execEnv, token, sourceFileInfo, /* mapCompiled */ true), nameMap, libraryMap, () => this._buildModuleSymbolsMap(sourceFileInfo, libraryMap, options.includeUserSymbolsInAutoImport, token), completionItem, token);
1442
- });
1443
- }
1444
- renameModule(path, newPath, token) {
1445
- return this._runEvaluatorWithCancellationToken(token, () => {
1446
- if ((0, pathUtils_1.isFile)(this._fs, path)) {
1447
- const fileInfo = this.getSourceFileInfo(path);
1448
- if (!fileInfo) {
1449
- return undefined;
1450
- }
1451
- }
1452
- const renameModuleProvider = renameModuleProvider_1.RenameModuleProvider.createForModule(this._importResolver, this._configOptions, this._evaluator, path, newPath, token);
1453
- if (!renameModuleProvider) {
1454
- return undefined;
1455
- }
1456
- this._processModuleReferences(renameModuleProvider, renameModuleProvider.lastModuleName, path);
1457
- return { edits: renameModuleProvider.getEdits(), fileOperations: [] };
1458
- });
1459
- }
1460
- moveSymbolAtPosition(filePath, newFilePath, position, options, token) {
1461
- return this._runEvaluatorWithCancellationToken(token, () => {
1462
- var _a, _b;
1463
- const sourceFileExt = (0, pathUtils_1.getFileExtension)(filePath);
1464
- const destFileExt = (0, pathUtils_1.getFileExtension)(newFilePath);
1465
- if (sourceFileExt.toLowerCase() !== destFileExt.toLowerCase()) {
1466
- // Don't allow moving a symbol from py to pyi or vice versa.
1467
- return undefined;
1468
- }
1469
- const fileInfo = this.getSourceFileInfo(filePath);
1470
- if (!fileInfo) {
1471
- return undefined;
1472
- }
1473
- const newFileInfo = this.getBoundSourceFileInfo(newFilePath);
1474
- if (fileInfo === newFileInfo) {
1475
- // Can't move symbol to the same file.
1476
- return undefined;
1477
- }
1478
- this._bindFile(fileInfo);
1479
- const parseResults = fileInfo.sourceFile.getParseResults();
1480
- if (!parseResults) {
1481
- return undefined;
1482
- }
1483
- const offset = (0, positionUtils_1.convertPositionToOffset)(position, parseResults.tokenizerOutput.lines);
1484
- if (offset === undefined) {
1485
- return undefined;
1486
- }
1487
- const node = (0, parseTreeUtils_1.findNodeByOffset)(parseResults.parseTree, offset);
1488
- if (node === undefined) {
1489
- return undefined;
1490
- }
1491
- // If this isn't a name node, there are no references to be found.
1492
- if (node.nodeType !== 38 /* Name */ ||
1493
- !renameModuleProvider_1.RenameModuleProvider.canMoveSymbol(this._configOptions, this._evaluator, node)) {
1494
- return undefined;
1495
- }
1496
- // We will try to
1497
- // 1. Find symbol to move.
1498
- // 2. Update all references to the symbol to new location.
1499
- // 3. Remove the existing symbol.
1500
- // 4. Insert the symbol to the destination module.
1501
- // 5. Insert imports required for the symbol moved to the destination module.
1502
- // 6. Remove import no longer needed from the original module.
1503
- //
1504
- // Here all changes are done to edits, no features in LS will apply changes to
1505
- // program directly. All modification is done through LSP by a edit request so
1506
- // things like undo or edit stacks UI works.
1507
- // 1. Find symbol to move.
1508
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1509
- const declarations = documentSymbolCollector_1.DocumentSymbolCollector.getDeclarationsForNode(node, this._evaluator,
1510
- /* resolveLocalNames */ false, documentSymbolCollector_1.DocumentSymbolCollectorUseCase.Rename, token, this._createSourceMapper(execEnv, token, fileInfo));
1511
- const renameModuleProvider = renameModuleProvider_1.RenameModuleProvider.createForSymbol(this._importResolver, this._configOptions, this._evaluator, filePath, newFilePath, declarations, token);
1512
- if (!renameModuleProvider) {
1513
- return undefined;
1514
- }
1515
- // 2. Update affected references.
1516
- this._processModuleReferences(renameModuleProvider, node.value, filePath);
1517
- // 3. Remove existing symbols.
1518
- const sourceDecl = renameModuleProvider.declarations.find((d) => d.node && (0, pathUtils_1.getFileExtension)(d.path) === sourceFileExt);
1519
- if (!sourceDecl) {
1520
- // Can't find symbol we can move.
1521
- return undefined;
1522
- }
1523
- const symbolRange = renameModuleProvider_1.RenameModuleProvider.getSymbolTextRange(parseResults, sourceDecl);
1524
- const importAdder = new importAdder_1.ImportAdder(this._configOptions, this._importResolver, this._evaluator);
1525
- const collectedImports = importAdder.collectImportsForSymbolsUsed(parseResults, symbolRange, token);
1526
- let insertionPoint = 0;
1527
- let insertionIndentation = 0;
1528
- const newFileParseResults = newFileInfo === null || newFileInfo === void 0 ? void 0 : newFileInfo.sourceFile.getParseResults();
1529
- if (newFileParseResults) {
1530
- const insertBefore = renameModuleProvider.tryGetFirstSymbolUsage(newFileParseResults);
1531
- insertionPoint = (0, insertionPointUtils_1.getInsertionPointForSymbolUnderModule)(this._evaluator, newFileParseResults, node.value, {
1532
- symbolDeclToIgnore: sourceDecl.path,
1533
- insertBefore,
1534
- });
1535
- if (insertionPoint === undefined) {
1536
- // No place to insert the symbol.
1537
- return undefined;
1538
- }
1539
- insertionIndentation = (0, indentationUtils_1.getModuleStatementIndentation)(newFileParseResults);
1540
- }
1541
- const reindentResult = (0, indentationUtils_1.reindentSpan)(parseResults, symbolRange, insertionIndentation);
1542
- const fullRange = renameModuleProvider_1.RenameModuleProvider.getSymbolFullStatementTextRange(parseResults, sourceDecl);
1543
- renameModuleProvider.textEditTracker.addEdit(filePath, (0, positionUtils_1.convertTextRangeToRange)(textRange_1.TextRange.combine([reindentResult.originalSpan, fullRange]), parseResults.tokenizerOutput.lines), '');
1544
- // 4. Add the symbol to the destination file.
1545
- const fileOperations = [];
1546
- let codeSnippetToInsert = reindentResult.text;
1547
- if (newFileParseResults) {
1548
- const range = (0, positionUtils_1.convertTextRangeToRange)({ start: insertionPoint, length: 0 }, newFileParseResults.tokenizerOutput.lines);
1549
- // If we are adding at the end of line (ex, end of a file),
1550
- // add new lines.
1551
- const newLinesToAdd = _getNumberOfBlankLinesToInsert(newFileParseResults, sourceDecl, range.end);
1552
- codeSnippetToInsert = '\n'.repeat(newLinesToAdd) + codeSnippetToInsert;
1553
- renameModuleProvider.textEditTracker.addEdit(newFilePath, range, codeSnippetToInsert);
1554
- }
1555
- else {
1556
- fileOperations.push({ kind: 'create', filePath: newFilePath });
1557
- renameModuleProvider.textEditTracker.addEdit(newFilePath, (0, textRange_1.getEmptyRange)(), codeSnippetToInsert);
1558
- }
1559
- // 5. Insert imports required for the symbol moved to the destination module.
1560
- //
1561
- // Since step 5 and 6 can create nested edits, we clone the program and apply all changes to re-calculate
1562
- // edits we need to apply to the destination file. The same workflow as `fix all` but done in program level
1563
- // not service level.
1564
- const cloned = this.clone();
1565
- let edits = renameModuleProvider.getEdits();
1566
- const textAfterSymbolAdded = (0, workspaceEditUtils_1.applyTextEditsToString)(edits.filter((v) => v.filePath === newFilePath), (_a = newFileParseResults === null || newFileParseResults === void 0 ? void 0 : newFileParseResults.tokenizerOutput.lines) !== null && _a !== void 0 ? _a : new textRangeCollection_1.TextRangeCollection([]), (_b = newFileInfo === null || newFileInfo === void 0 ? void 0 : newFileInfo.sourceFile.getFileContent()) !== null && _b !== void 0 ? _b : '');
1567
- _updateFileContent(cloned, newFilePath, textAfterSymbolAdded);
1568
- const textAfterImportsAdded = _tryGetTextAfterImportsAdded(cloned, newFilePath, collectedImports, insertionPoint, token);
1569
- edits = _updateFileEditActions(edits, newFilePath, newFileParseResults, textAfterSymbolAdded, textAfterImportsAdded);
1570
- // 6. Remove imports no longer required from original module.
1571
- const textAfterSymbolRemoved = (0, workspaceEditUtils_1.applyTextEditsToString)(edits.filter((v) => v.filePath === filePath), parseResults.tokenizerOutput.lines, fileInfo.sourceFile.getFileContent());
1572
- _updateFileContent(cloned, filePath, textAfterSymbolRemoved);
1573
- const textAfterUnusedImportsRemoved = _tryGetTextAfterUnusedImportsRemoved(cloned, filePath, collectedImports, 0, token);
1574
- edits = _updateFileEditActions(edits, filePath, parseResults, textAfterSymbolRemoved, textAfterUnusedImportsRemoved);
1575
- cloned.dispose();
1576
- return {
1577
- edits,
1578
- fileOperations,
1579
- };
1580
- function _updateFileEditActions(edits, filePath, parseResults, oldText, newText) {
1581
- if (newText === undefined || oldText === newText) {
1582
- return edits;
1583
- }
1584
- // There were nested edits. Replace whole file.
1585
- edits = edits.filter((v) => v.filePath !== filePath);
1586
- edits.push({
1587
- filePath,
1588
- range: parseResults
1589
- ? (0, positionUtils_1.convertTextRangeToRange)(parseResults.parseTree, parseResults.tokenizerOutput.lines)
1590
- : (0, textRange_1.getEmptyRange)(),
1591
- replacementText: newText,
1592
- });
1593
- return edits;
1594
- }
1595
- function _tryGetTextAfterImportsAdded(cloned, filePath, importData, insertionPoint, token) {
1596
- const sourceFile = cloned.getBoundSourceFile(filePath);
1597
- const parseResults = sourceFile === null || sourceFile === void 0 ? void 0 : sourceFile.getParseResults();
1598
- if (!parseResults) {
1599
- return undefined;
1600
- }
1601
- const insertAddEdits = importAdder.applyImports(importData, filePath, parseResults, insertionPoint, options.importFormat, token);
1602
- return (0, workspaceEditUtils_1.applyTextEditsToString)(insertAddEdits, parseResults.tokenizerOutput.lines, sourceFile.getFileContent());
1603
- }
1604
- function _tryGetTextAfterUnusedImportsRemoved(cloned, filePath, importData, attempt, token) {
1605
- var _a;
1606
- (0, cancellationUtils_1.throwIfCancellationRequested)(token);
1607
- cloned.analyzeFile(filePath, token);
1608
- const sourceFile = cloned.getBoundSourceFile(filePath);
1609
- const parseResults = sourceFile === null || sourceFile === void 0 ? void 0 : sourceFile.getParseResults();
1610
- if (!parseResults) {
1611
- return undefined;
1150
+ let thirdPartyImportAllowed = this._configOptions.useLibraryCodeForTypes ||
1151
+ (importResult.importType === 1 /* ThirdParty */ && !!importResult.pyTypedInfo) ||
1152
+ (importResult.importType === 2 /* Local */ && importer.isThirdPartyPyTypedPresent);
1153
+ if (importResult.importType === 1 /* ThirdParty */ ||
1154
+ (importer.isThirdPartyImport && importResult.importType === 2 /* Local */)) {
1155
+ if (this._allowedThirdPartyImports) {
1156
+ if (importResult.isRelative) {
1157
+ // If it's a relative import, we'll allow it because the
1158
+ // importer was already deemed to be allowed.
1159
+ thirdPartyImportAllowed = true;
1612
1160
  }
1613
- const tracker = new textEditTracker_1.TextEditTracker();
1614
- for (const diagnostic of cloned
1615
- .getDiagnosticsForRange(filePath, (0, positionUtils_1.convertTextRangeToRange)(parseResults.parseTree, parseResults.tokenizerOutput.lines))
1616
- .filter((d) => {
1617
- var _a;
1618
- return d.category === 3 /* UnusedCode */ &&
1619
- ((_a = d.getActions()) === null || _a === void 0 ? void 0 : _a.some((a) => a.action === "pyright.unusedImport" /* unusedImport */));
1620
- })) {
1621
- const nameNode = (0, parseTreeUtils_1.findNodeByPosition)(parseResults.parseTree, diagnostic.range.start, parseResults.tokenizerOutput.lines);
1622
- if ((nameNode === null || nameNode === void 0 ? void 0 : nameNode.nodeType) !== 38 /* Name */) {
1623
- continue;
1161
+ else if (this._allowedThirdPartyImports.some((importName) => {
1162
+ // If this import name is the one that was explicitly
1163
+ // allowed or is a child of that import name,
1164
+ // it's considered allowed.
1165
+ if (importResult.importName === importName) {
1166
+ return true;
1624
1167
  }
1625
- // decl is synthesized. there is no node associated with the decl.
1626
- // ex) import a or import a.b
1627
- const dottedName1 = ((_a = nameNode.parent) === null || _a === void 0 ? void 0 : _a.nodeType) === 37 /* ModuleName */ ? nameNode.parent.nameParts : [nameNode];
1628
- for (const [decl, names] of importData.declarations) {
1629
- if (decl.node) {
1630
- if (textRange_1.TextRange.containsRange(decl.node, nameNode)) {
1631
- tracker.removeNodes({ node: nameNode, parseResults: parseResults });
1632
- break;
1633
- }
1634
- }
1635
- const dottedName2 = (0, parseTreeUtils_1.getDottedName)((0, parseTreeUtils_1.getDottedNameWithGivenNodeAsLastName)(names[0]));
1636
- if (dottedName2 && (0, collectionUtils_1.arrayEquals)(dottedName1, dottedName2, (e1, e2) => e1.value === e2.value)) {
1637
- tracker.removeNodes({ node: nameNode, parseResults: parseResults });
1638
- break;
1639
- }
1168
+ if (importResult.importName.startsWith(importName + '.')) {
1169
+ return true;
1640
1170
  }
1171
+ return false;
1172
+ })) {
1173
+ thirdPartyImportAllowed = true;
1641
1174
  }
1642
- const oldText = sourceFile.getFileContent();
1643
- const newText = (0, workspaceEditUtils_1.applyTextEditsToString)(tracker.getEdits(token).filter((v) => v.filePath === filePath), parseResults.tokenizerOutput.lines, oldText);
1644
- // We will attempt to remove unused imports multiple times since removing 1 unused import
1645
- // could make another import unused. This is due to how we calculate which import is not used.
1646
- // ex) import os, os.path, os.path.xxx
1647
- // `os.path` and `os.path.xxx` will be marked as used due to `import os`.
1648
- // once `os` is removed `os.path` will be marked as unused and so on.
1649
- // We will attempt to remove those chained unused imports up to 10 chain.
1650
- if (attempt > 10 || oldText === newText) {
1651
- return newText;
1652
- }
1653
- _updateFileContent(cloned, filePath, newText);
1654
- return _tryGetTextAfterUnusedImportsRemoved(cloned, filePath, importData, attempt + 1, token);
1655
- }
1656
- });
1657
- function _updateFileContent(cloned, filePath, text) {
1658
- var _a, _b;
1659
- const info = cloned.getSourceFileInfo(filePath);
1660
- const version = info ? ((_a = info.sourceFile.getClientVersion()) !== null && _a !== void 0 ? _a : 0) + 1 : 0;
1661
- const chainedFilePath = info ? (_b = info.chainedSourceFile) === null || _b === void 0 ? void 0 : _b.sourceFile.getFilePath() : undefined;
1662
- const ipythonMode = info ? info.sourceFile.getIPythonMode() : sourceFile_1.IPythonMode.None;
1663
- const isTracked = info ? info.isTracked : true;
1664
- const realFilePath = info ? info.sourceFile.getRealFilePath() : filePath;
1665
- cloned.setFileOpened(filePath, version, [{ text }], {
1666
- chainedFilePath,
1667
- ipythonMode,
1668
- isTracked,
1669
- realFilePath,
1670
- });
1671
- }
1672
- function _getNumberOfBlankLinesToInsert(parseResults, decl, position) {
1673
- if (position.line === 0 && position.character === 0) {
1674
- return 0;
1675
1175
  }
1676
- let previousStatement;
1677
- const offset = (0, positionUtils_1.convertPositionToOffset)(position, parseResults.tokenizerOutput.lines);
1678
- if (offset && parseResults.parseTree.statements.length > 0) {
1679
- previousStatement = parseResults.parseTree.statements.reduce((prev, curr) => offset < curr.start ? prev : curr);
1680
- }
1681
- // This basically try to add some blank lines after the last line with text.
1682
- let linesToAdd = 0;
1683
- if (previousStatement) {
1684
- if ((0, declaration_1.isVariableDeclaration)(decl)) {
1685
- switch (previousStatement.nodeType) {
1686
- case 47 /* StatementList */:
1687
- // Small statement such as call, assignment, etc.
1688
- linesToAdd = 0;
1689
- break;
1690
- case 10 /* Class */:
1691
- case 28 /* Function */:
1692
- linesToAdd = 2;
1693
- break;
1694
- default:
1695
- // any other statement such as if, while, etc. we will add 1 blank line.
1696
- linesToAdd = 1;
1697
- }
1698
- }
1699
- else {
1700
- linesToAdd = 2;
1701
- }
1176
+ else if (importer.isThirdPartyImport && this._configOptions.useLibraryCodeForTypes) {
1177
+ // If the importing file is a third-party import, allow importing of
1178
+ // additional third-party imports. This supports the case where the importer
1179
+ // is in a py.typed library but is importing from another non-py.typed
1180
+ // library. It also supports the case where someone explicitly opens a
1181
+ // library source file in their editor.
1182
+ thirdPartyImportAllowed = true;
1702
1183
  }
1703
- // If the position is not at the beginning of the line, we need to add 1 more '\n'
1704
- // to start from blank line.
1705
- linesToAdd += position.character !== 0 ? 1 : 0;
1706
- // If there are already blank lines, we only add the difference.
1707
- const desiredBlankLines = linesToAdd;
1708
- const startingLine = position.character !== 0 ? position.line : position.line - 1;
1709
- for (let i = 0; i < desiredBlankLines; i++) {
1710
- const currentLine = startingLine - i;
1711
- if (currentLine < 0 || !(0, parseTreeUtils_1.isBlankLine)(parseResults, currentLine)) {
1712
- break;
1713
- }
1714
- linesToAdd--;
1184
+ else if (importResult.isNamespacePackage &&
1185
+ importResult.filteredImplicitImports.some((implicitImport) => !!implicitImport.pyTypedInfo)) {
1186
+ // Handle the case where the import targets a namespace package, and a
1187
+ // submodule contained within it has a py.typed marker.
1188
+ thirdPartyImportAllowed = true;
1715
1189
  }
1716
- return linesToAdd;
1717
- }
1718
- }
1719
- clone() {
1720
- var _a;
1721
- const program = new Program(this._importResolver, this._configOptions, this._console, new logTracker_1.LogTracker(this._console, 'Cloned'));
1722
- // Cloned program will use whatever user files the program currently has.
1723
- const userFiles = this.getUserFiles();
1724
- program.setTrackedFiles(userFiles.map((i) => i.sourceFile.getFilePath()));
1725
- program.markAllFilesDirty(true);
1726
- // Make sure we keep editor content (open file) which could be different than one in the file system.
1727
- for (const fileInfo of this.getOpened()) {
1728
- const version = fileInfo.sourceFile.getClientVersion();
1729
- if (version === undefined) {
1730
- continue;
1190
+ // Some libraries ship with stub files that import from non-stubs. Don't
1191
+ // explore those.
1192
+ // Don't explore any third-party files unless they're type stub files
1193
+ // or we've been told explicitly that third-party imports are OK.
1194
+ if (!isImportStubFile) {
1195
+ return thirdPartyImportAllowed;
1731
1196
  }
1732
- program.setFileOpened(fileInfo.sourceFile.getFilePath(), version, [{ text: fileInfo.sourceFile.getOpenFileContents() }], {
1733
- chainedFilePath: (_a = fileInfo.chainedSourceFile) === null || _a === void 0 ? void 0 : _a.sourceFile.getFilePath(),
1734
- ipythonMode: fileInfo.sourceFile.getIPythonMode(),
1735
- isTracked: fileInfo.isTracked,
1736
- realFilePath: fileInfo.sourceFile.getRealFilePath(),
1737
- });
1738
1197
  }
1739
- return program;
1198
+ return true;
1740
1199
  }
1741
- canRenameSymbolAtPosition(filePath, position, isDefaultWorkspace, allowModuleRename, token) {
1742
- return this._runEvaluatorWithCancellationToken(token, () => {
1743
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1744
- if (!sourceFileInfo) {
1745
- return undefined;
1746
- }
1747
- this._bindFile(sourceFileInfo);
1748
- const referencesResult = this._getReferenceResult(sourceFileInfo, filePath, position, allowModuleRename, token);
1749
- if (!referencesResult) {
1750
- return undefined;
1751
- }
1752
- if (referencesResult.containsOnlyImportDecls &&
1753
- !this._supportRenameModule(referencesResult.declarations, isDefaultWorkspace)) {
1754
- return undefined;
1200
+ _updateSourceFileImports(sourceFileInfo, options) {
1201
+ const filesAdded = [];
1202
+ // Get the new list of imports and see if it changed from the last
1203
+ // list of imports for this file.
1204
+ const imports = sourceFileInfo.sourceFile.getImports();
1205
+ // Create a local function that determines whether the import should
1206
+ // be considered a "third-party import" and whether it is coming from
1207
+ // a third-party package that claims to be typed. An import is
1208
+ // considered third-party if it is external to the importer
1209
+ // or is internal but the importer is itself a third-party package.
1210
+ const getThirdPartyImportInfo = (importResult) => {
1211
+ let isThirdPartyImport = false;
1212
+ let isPyTypedPresent = false;
1213
+ if (importResult.importType === 1 /* ThirdParty */) {
1214
+ isThirdPartyImport = true;
1215
+ if (importResult.pyTypedInfo) {
1216
+ isPyTypedPresent = true;
1217
+ }
1755
1218
  }
1756
- const renameMode = this._getRenameSymbolMode(sourceFileInfo, referencesResult, isDefaultWorkspace);
1757
- if (renameMode === 'none') {
1758
- return undefined;
1219
+ else if (sourceFileInfo.isThirdPartyImport && importResult.importType === 2 /* Local */) {
1220
+ isThirdPartyImport = true;
1221
+ if (sourceFileInfo.isThirdPartyPyTypedPresent) {
1222
+ isPyTypedPresent = true;
1223
+ }
1759
1224
  }
1760
- // Return the range of the symbol.
1761
- const parseResult = sourceFileInfo.sourceFile.getParseResults();
1762
1225
  return {
1763
- range: (0, positionUtils_1.convertTextRangeToRange)(referencesResult.nodeAtOffset, parseResult.tokenizerOutput.lines),
1764
- declarations: referencesResult.declarations,
1226
+ isThirdPartyImport,
1227
+ isPyTypedPresent,
1765
1228
  };
1766
- });
1767
- }
1768
- renameSymbolAtPosition(filePath, position, newName, isDefaultWorkspace, allowModuleRename, token) {
1769
- return this._runEvaluatorWithCancellationToken(token, () => {
1770
- var _a;
1771
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1772
- if (!sourceFileInfo) {
1773
- return undefined;
1774
- }
1775
- this._bindFile(sourceFileInfo);
1776
- const referencesResult = this._getReferenceResult(sourceFileInfo, filePath, position, allowModuleRename, token);
1777
- if (!referencesResult) {
1778
- return undefined;
1229
+ };
1230
+ // Create a map of unique imports, since imports can appear more than once.
1231
+ const newImportPathMap = new Map();
1232
+ // Add chained source file as import if it exists.
1233
+ if (sourceFileInfo.chainedSourceFile) {
1234
+ if (sourceFileInfo.chainedSourceFile.sourceFile.isFileDeleted()) {
1235
+ sourceFileInfo.chainedSourceFile = undefined;
1779
1236
  }
1780
- if (referencesResult.containsOnlyImportDecls) {
1781
- // All decls must be on a user file.
1782
- if (!this._supportRenameModule(referencesResult.declarations, isDefaultWorkspace)) {
1783
- return undefined;
1784
- }
1785
- const moduleInfo = renameModuleProvider_1.RenameModuleProvider.getRenameModulePathInfo(renameModuleProvider_1.RenameModuleProvider.getRenameModulePath(referencesResult.declarations), newName);
1786
- if (!moduleInfo) {
1787
- // Can't figure out module to rename.
1788
- return undefined;
1789
- }
1790
- const editActions = this.renameModule(moduleInfo.filePath, moduleInfo.newFilePath, token);
1791
- // Add file system rename.
1792
- editActions === null || editActions === void 0 ? void 0 : editActions.fileOperations.push({
1793
- kind: 'rename',
1794
- oldFilePath: moduleInfo.filePath,
1795
- newFilePath: moduleInfo.newFilePath,
1237
+ else {
1238
+ const filePath = sourceFileInfo.chainedSourceFile.sourceFile.getFilePath();
1239
+ newImportPathMap.set((0, pathUtils_1.normalizePathCase)(this.fileSystem, filePath), {
1240
+ path: filePath,
1241
+ isTypeshedFile: false,
1242
+ isThirdPartyImport: false,
1243
+ isPyTypedPresent: false,
1796
1244
  });
1797
- if ((0, sourceMapper_1.isStubFile)(moduleInfo.filePath)) {
1798
- const matchingFiles = this._importResolver.getSourceFilesFromStub(moduleInfo.filePath, this._configOptions.findExecEnvironment(filePath),
1799
- /* mapCompiled */ false);
1800
- for (const matchingFile of matchingFiles) {
1801
- const matchingFileInfo = renameModuleProvider_1.RenameModuleProvider.getRenameModulePathInfo(matchingFile, newName);
1802
- if (matchingFileInfo) {
1803
- editActions === null || editActions === void 0 ? void 0 : editActions.fileOperations.push({
1804
- kind: 'rename',
1805
- oldFilePath: matchingFileInfo.filePath,
1806
- newFilePath: matchingFileInfo.newFilePath,
1245
+ }
1246
+ }
1247
+ imports.forEach((importResult) => {
1248
+ if (importResult.isImportFound) {
1249
+ if (this._isImportAllowed(sourceFileInfo, importResult, importResult.isStubFile)) {
1250
+ if (importResult.resolvedPaths.length > 0) {
1251
+ const filePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
1252
+ if (filePath) {
1253
+ const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
1254
+ newImportPathMap.set((0, pathUtils_1.normalizePathCase)(this.fileSystem, filePath), {
1255
+ path: filePath,
1256
+ isTypeshedFile: !!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
1257
+ isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
1258
+ isPyTypedPresent: thirdPartyTypeInfo.isPyTypedPresent,
1807
1259
  });
1808
1260
  }
1809
1261
  }
1810
1262
  }
1811
- return editActions;
1812
- }
1813
- const renameMode = this._getRenameSymbolMode(sourceFileInfo, referencesResult, isDefaultWorkspace);
1814
- switch (renameMode) {
1815
- case 'singleFileMode':
1816
- sourceFileInfo.sourceFile.addReferences(referencesResult, true, this._evaluator, token);
1817
- break;
1818
- case 'multiFileMode': {
1819
- for (const curSourceFileInfo of this._sourceFileList) {
1820
- // Make sure we only add user code to the references to prevent us
1821
- // from accidentally changing third party library or type stub.
1822
- if ((0, sourceFileInfoUtils_1.isUserCode)(curSourceFileInfo)) {
1823
- // Make sure searching symbol name exists in the file.
1824
- const content = (_a = curSourceFileInfo.sourceFile.getFileContent()) !== null && _a !== void 0 ? _a : '';
1825
- if (!referencesResult.symbolNames.some((s) => content.search(s) >= 0)) {
1826
- continue;
1263
+ importResult.filteredImplicitImports.forEach((implicitImport) => {
1264
+ if (this._isImportAllowed(sourceFileInfo, importResult, implicitImport.isStubFile)) {
1265
+ if (!implicitImport.isNativeLib) {
1266
+ const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
1267
+ newImportPathMap.set((0, pathUtils_1.normalizePathCase)(this.fileSystem, implicitImport.path), {
1268
+ path: implicitImport.path,
1269
+ isTypeshedFile: !!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
1270
+ isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
1271
+ isPyTypedPresent: thirdPartyTypeInfo.isPyTypedPresent,
1272
+ });
1273
+ }
1274
+ }
1275
+ });
1276
+ // If the stub was found but the non-stub (source) file was not, dump
1277
+ // the failure to the log for diagnostic purposes.
1278
+ if (importResult.nonStubImportResult && !importResult.nonStubImportResult.isImportFound) {
1279
+ // We'll skip this for imports from within stub files and imports that target
1280
+ // stdlib typeshed stubs because many of these are known to not have
1281
+ // associated source files, and we don't want to fill the logs with noise.
1282
+ if (!sourceFileInfo.sourceFile.isStubFile() && !importResult.isStdlibTypeshedFile) {
1283
+ if (options.verboseOutput) {
1284
+ this._console.info(`Could not resolve source for '${importResult.importName}' ` +
1285
+ `in file '${sourceFileInfo.sourceFile.getFilePath()}'`);
1286
+ if (importResult.nonStubImportResult.importFailureInfo) {
1287
+ importResult.nonStubImportResult.importFailureInfo.forEach((diag) => {
1288
+ this._console.info(` ${diag}`);
1289
+ });
1827
1290
  }
1828
- this._bindFile(curSourceFileInfo, content);
1829
- curSourceFileInfo.sourceFile.addReferences(referencesResult, true, this._evaluator, token);
1830
1291
  }
1831
- // This operation can consume significant memory, so check
1832
- // for situations where we need to discard the type cache.
1833
- this._handleMemoryHighUsage();
1834
1292
  }
1835
- break;
1836
1293
  }
1837
- case 'none':
1838
- // Rename is not allowed.
1839
- // ex) rename symbols from libraries.
1840
- return undefined;
1841
- default:
1842
- (0, debug_1.assertNever)(renameMode);
1843
1294
  }
1844
- const edits = [];
1845
- referencesResult.locations.forEach((loc) => {
1846
- edits.push({
1847
- filePath: loc.path,
1848
- range: loc.range,
1849
- replacementText: newName,
1850
- });
1851
- });
1852
- return { edits, fileOperations: [] };
1295
+ else if (options.verboseOutput) {
1296
+ this._console.info(`Could not import '${importResult.importName}' ` +
1297
+ `in file '${sourceFileInfo.sourceFile.getFilePath()}'`);
1298
+ if (importResult.importFailureInfo) {
1299
+ importResult.importFailureInfo.forEach((diag) => {
1300
+ this._console.info(` ${diag}`);
1301
+ });
1302
+ }
1303
+ }
1853
1304
  });
1854
- }
1855
- getCallForPosition(filePath, position, token) {
1856
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1857
- if (!sourceFileInfo) {
1858
- return undefined;
1859
- }
1860
- this._bindFile(sourceFileInfo);
1861
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1862
- const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(this._createSourceMapper(execEnv, token, sourceFileInfo), position, this._evaluator, undefined, documentSymbolCollector_1.DocumentSymbolCollectorUseCase.Reference, token);
1863
- if (!referencesResult || referencesResult.declarations.length === 0) {
1864
- return undefined;
1865
- }
1866
- const { targetDecl, callItemUri, symbolName } = callHierarchyProvider_1.CallHierarchyProvider.getTargetDeclaration(referencesResult, filePath);
1867
- return callHierarchyProvider_1.CallHierarchyProvider.getCallForDeclaration(symbolName, targetDecl, this._evaluator, token, callItemUri);
1868
- }
1869
- getIncomingCallsForPosition(filePath, position, token) {
1870
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1871
- if (!sourceFileInfo) {
1872
- return undefined;
1873
- }
1874
- this._bindFile(sourceFileInfo);
1875
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1876
- const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(this._createSourceMapper(execEnv, token, sourceFileInfo), position, this._evaluator, undefined, documentSymbolCollector_1.DocumentSymbolCollectorUseCase.Reference, token);
1877
- if (!referencesResult || referencesResult.declarations.length === 0) {
1878
- return undefined;
1879
- }
1880
- const { targetDecl, symbolName } = callHierarchyProvider_1.CallHierarchyProvider.getTargetDeclaration(referencesResult, filePath);
1881
- let items = [];
1882
- const sourceFiles = targetDecl.type === 8 /* Alias */ ? [sourceFileInfo] : this._sourceFileList;
1883
- for (const curSourceFileInfo of sourceFiles) {
1884
- if ((0, sourceFileInfoUtils_1.isUserCode)(curSourceFileInfo) || curSourceFileInfo.isOpenByClient) {
1885
- this._bindFile(curSourceFileInfo);
1886
- const itemsToAdd = callHierarchyProvider_1.CallHierarchyProvider.getIncomingCallsForDeclaration(curSourceFileInfo.sourceFile.getFilePath(), symbolName, targetDecl, curSourceFileInfo.sourceFile.getParseResults(), this._evaluator, token);
1887
- if (itemsToAdd) {
1888
- items = items.concat(...itemsToAdd);
1305
+ const updatedImportMap = new Map();
1306
+ sourceFileInfo.imports.forEach((importInfo) => {
1307
+ const oldFilePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, importInfo.sourceFile.getFilePath());
1308
+ // A previous import was removed.
1309
+ if (!newImportPathMap.has(oldFilePath)) {
1310
+ importInfo.importedBy = importInfo.importedBy.filter((fi) => (0, pathUtils_1.normalizePathCase)(this.fileSystem, fi.sourceFile.getFilePath()) !==
1311
+ (0, pathUtils_1.normalizePathCase)(this.fileSystem, sourceFileInfo.sourceFile.getFilePath()));
1312
+ }
1313
+ else {
1314
+ updatedImportMap.set(oldFilePath, importInfo);
1315
+ }
1316
+ });
1317
+ // See if there are any new imports to be added.
1318
+ newImportPathMap.forEach((importInfo, normalizedImportPath) => {
1319
+ if (!updatedImportMap.has(normalizedImportPath)) {
1320
+ // We found a new import to add. See if it's already part
1321
+ // of the program.
1322
+ let importedFileInfo = this.getSourceFileInfo(importInfo.path);
1323
+ if (!importedFileInfo) {
1324
+ const importName = this._getImportNameForFile(importInfo.path);
1325
+ const sourceFile = new sourceFile_1.SourceFile(this.fileSystem, importInfo.path, importName, importInfo.isThirdPartyImport, importInfo.isPyTypedPresent, this._isEditMode, this._console, this._logTracker);
1326
+ importedFileInfo = {
1327
+ sourceFile,
1328
+ isTracked: false,
1329
+ isOpenByClient: false,
1330
+ isTypeshedFile: importInfo.isTypeshedFile,
1331
+ isThirdPartyImport: importInfo.isThirdPartyImport,
1332
+ isThirdPartyPyTypedPresent: importInfo.isPyTypedPresent,
1333
+ diagnosticsVersion: undefined,
1334
+ imports: [],
1335
+ importedBy: [],
1336
+ shadows: [],
1337
+ shadowedBy: [],
1338
+ };
1339
+ this._addToSourceFileListAndMap(importedFileInfo);
1340
+ filesAdded.push(importedFileInfo);
1889
1341
  }
1890
- // This operation can consume significant memory, so check
1891
- // for situations where we need to discard the type cache.
1892
- this._handleMemoryHighUsage();
1342
+ importedFileInfo.importedBy.push(sourceFileInfo);
1343
+ updatedImportMap.set(normalizedImportPath, importedFileInfo);
1893
1344
  }
1345
+ });
1346
+ // Update the imports list. It should now map the set of imports
1347
+ // specified by the source file.
1348
+ sourceFileInfo.imports = [];
1349
+ newImportPathMap.forEach((_, path) => {
1350
+ if (this.getSourceFileInfo(path)) {
1351
+ sourceFileInfo.imports.push(this.getSourceFileInfo(path));
1352
+ }
1353
+ });
1354
+ // Resolve the builtins import for the file. This needs to be
1355
+ // analyzed before the file can be analyzed.
1356
+ sourceFileInfo.builtinsImport = undefined;
1357
+ const builtinsImport = sourceFileInfo.sourceFile.getBuiltinsImport();
1358
+ if (builtinsImport && builtinsImport.isImportFound) {
1359
+ const resolvedBuiltinsPath = builtinsImport.resolvedPaths[builtinsImport.resolvedPaths.length - 1];
1360
+ sourceFileInfo.builtinsImport = this.getSourceFileInfo(resolvedBuiltinsPath);
1894
1361
  }
1895
- return items;
1896
- }
1897
- getOutgoingCallsForPosition(filePath, position, token) {
1898
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1899
- if (!sourceFileInfo) {
1900
- return undefined;
1901
- }
1902
- this._bindFile(sourceFileInfo);
1903
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1904
- const referencesResult = sourceFileInfo.sourceFile.getDeclarationForPosition(this._createSourceMapper(execEnv, token, sourceFileInfo), position, this._evaluator, undefined, documentSymbolCollector_1.DocumentSymbolCollectorUseCase.Reference, token);
1905
- if (!referencesResult || referencesResult.declarations.length === 0) {
1906
- return undefined;
1907
- }
1908
- const { targetDecl } = callHierarchyProvider_1.CallHierarchyProvider.getTargetDeclaration(referencesResult, filePath);
1909
- return callHierarchyProvider_1.CallHierarchyProvider.getOutgoingCallsForDeclaration(targetDecl, sourceFileInfo.sourceFile.getParseResults(), this._evaluator, token);
1910
- }
1911
- performQuickAction(filePath, command, args, token) {
1912
- const sourceFileInfo = this.getSourceFileInfo(filePath);
1913
- if (!sourceFileInfo) {
1914
- return undefined;
1362
+ // Resolve the ipython display import for the file. This needs to be
1363
+ // analyzed before the file can be analyzed.
1364
+ sourceFileInfo.ipythonDisplayImport = undefined;
1365
+ const ipythonDisplayImport = sourceFileInfo.sourceFile.getIPythonDisplayImport();
1366
+ if (ipythonDisplayImport && ipythonDisplayImport.isImportFound) {
1367
+ const resolvedIPythonDisplayPath = ipythonDisplayImport.resolvedPaths[ipythonDisplayImport.resolvedPaths.length - 1];
1368
+ sourceFileInfo.ipythonDisplayImport = this.getSourceFileInfo(resolvedIPythonDisplayPath);
1915
1369
  }
1916
- this._bindFile(sourceFileInfo);
1917
- return sourceFileInfo.sourceFile.performQuickAction(command, args, token);
1918
- }
1919
- // Returns a value from 0 to 1 (or more) indicating how "full" the cache is
1920
- // relative to some predetermined high-water mark. We'll compute this value
1921
- // based on two easy-to-compute metrics: the number of entries in the type
1922
- // cache and the number of parsed files.
1923
- getCacheUsage() {
1924
- const typeCacheEntryCount = this._evaluator.getTypeCacheEntryCount();
1925
- const entryCountRatio = typeCacheEntryCount / 750000;
1926
- const fileCountRatio = this._parsedFileCount / 1000;
1927
- return Math.max(entryCountRatio, fileCountRatio);
1370
+ return filesAdded;
1928
1371
  }
1929
- // Discards any cached information associated with this program.
1930
- emptyCache() {
1931
- this._createNewEvaluator();
1932
- this._discardCachedParseResults();
1933
- this._parsedFileCount = 0;
1934
- extensibility_1.Extensions.getProgramExtensions(this.rootPath).forEach((e) => (e.clearCache ? e.clearCache() : null));
1372
+ _removeSourceFileFromListAndMap(filePath, indexToRemove) {
1373
+ this._sourceFileMap.delete((0, pathUtils_1.normalizePathCase)(this.fileSystem, filePath));
1374
+ this._sourceFileList.splice(indexToRemove, 1);
1935
1375
  }
1936
- test_createSourceMapper(execEnv, from) {
1937
- return this._createSourceMapper(execEnv, vscode_languageserver_1.CancellationToken.None, /* from */ from, /* mapCompiled */ false);
1376
+ _addToSourceFileListAndMap(fileInfo) {
1377
+ const filePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, fileInfo.sourceFile.getFilePath());
1378
+ // We should never add a file with the same path twice.
1379
+ (0, debug_1.assert)(!this._sourceFileMap.has(filePath));
1380
+ this._sourceFileList.push(fileInfo);
1381
+ this._sourceFileMap.set(filePath, fileInfo);
1938
1382
  }
1939
- _getRenameSymbolMode(sourceFileInfo, referencesResult, isDefaultWorkspace) {
1940
- // We have 2 different cases
1941
- // Single file mode.
1942
- // 1. rename on default workspace (ex, standalone file mode).
1943
- // 2. rename local symbols.
1944
- // 3. rename symbols defined in the non user open file.
1945
- //
1946
- // and Multi file mode.
1947
- // 1. rename public symbols defined in user files on regular workspace (ex, open folder mode).
1948
- const userFile = (0, sourceFileInfoUtils_1.isUserCode)(sourceFileInfo);
1949
- if (isDefaultWorkspace ||
1950
- (userFile && !referencesResult.requiresGlobalSearch) ||
1951
- (!userFile &&
1952
- sourceFileInfo.isOpenByClient &&
1953
- referencesResult.declarations.every((d) => this.getSourceFileInfo(d.path) === sourceFileInfo))) {
1954
- return 'singleFileMode';
1955
- }
1956
- if (referencesResult.declarations.every((d) => (0, sourceFileInfoUtils_1.isUserCode)(this.getSourceFileInfo(d.path)))) {
1957
- return 'multiFileMode';
1958
- }
1959
- // Rename is not allowed.
1960
- // ex) rename symbols from libraries.
1961
- return 'none';
1962
- }
1963
- _supportRenameModule(declarations, isDefaultWorkspace) {
1964
- // Rename module is not supported for standalone file and all decls must be on a user file.
1965
- return !isDefaultWorkspace && declarations.every((d) => (0, sourceFileInfoUtils_1.isUserCode)(this.getSourceFileInfo(d.path)));
1966
- }
1967
- _getReferenceResult(sourceFileInfo, filePath, position, allowModuleRename, token) {
1968
- const execEnv = this._configOptions.findExecEnvironment(filePath);
1969
- const referencesResult = this._getDeclarationForPosition(sourceFileInfo, position, documentSymbolCollector_1.DocumentSymbolCollectorUseCase.Rename, this._createSourceMapper(execEnv, token), token);
1970
- if (!referencesResult) {
1971
- return undefined;
1383
+ static _getPrintTypeFlags(configOptions) {
1384
+ let flags = 0 /* None */;
1385
+ if (configOptions.diagnosticRuleSet.printUnknownAsAny) {
1386
+ flags |= 1 /* PrintUnknownWithAny */;
1972
1387
  }
1973
- if (allowModuleRename && referencesResult.containsOnlyImportDecls) {
1974
- return referencesResult;
1388
+ if (configOptions.diagnosticRuleSet.omitConditionalConstraint) {
1389
+ flags |= 64 /* OmitConditionalConstraint */;
1975
1390
  }
1976
- if (referencesResult.nonImportDeclarations.length === 0) {
1977
- // There is no symbol we can rename.
1978
- return undefined;
1391
+ if (configOptions.diagnosticRuleSet.omitTypeArgsIfUnknown) {
1392
+ flags |= 2 /* OmitTypeArgumentsIfUnknown */;
1393
+ }
1394
+ if (configOptions.diagnosticRuleSet.omitUnannotatedParamType) {
1395
+ flags |= 4 /* OmitUnannotatedParamType */;
1979
1396
  }
1980
- // Use declarations that doesn't contain import decls.
1981
- return new referencesProvider_1.ReferencesResult(referencesResult.requiresGlobalSearch, referencesResult.nodeAtOffset, referencesResult.symbolNames, referencesResult.nonImportDeclarations, referencesResult.useCase);
1397
+ if (configOptions.diagnosticRuleSet.pep604Printing) {
1398
+ flags |= 8 /* PEP604 */;
1399
+ }
1400
+ return flags;
1982
1401
  }
1983
- _getDeclarationForPosition(sourceFileInfo, position, useCase, sourceMapper, token, reporter) {
1984
- return sourceFileInfo.sourceFile.getDeclarationForPosition(sourceMapper, position, this._evaluator, reporter, useCase, token, Array.from((0, sourceFileInfoUtils_1.collectImportedByFiles)(sourceFileInfo)).map((fileInfo) => fileInfo.sourceFile));
1402
+ _getImportNameForFile(filePath) {
1403
+ // We allow illegal module names (e.g. names that include "-" in them)
1404
+ // because we want a unique name for each module even if it cannot be
1405
+ // imported through an "import" statement. It's important to have a
1406
+ // unique name in case two modules declare types with the same local
1407
+ // name. The type checker uses the fully-qualified (unique) module name
1408
+ // to differentiate between such types.
1409
+ const moduleNameAndType = this._importResolver.getModuleNameForImport(filePath, this._configOptions.getDefaultExecEnvironment(),
1410
+ /* allowIllegalModuleName */ true);
1411
+ return moduleNameAndType.moduleName;
1985
1412
  }
1986
- _processModuleReferences(renameModuleProvider, filteringText, currentFilePath) {
1987
- var _a;
1988
- // _sourceFileList contains every user files that match "include" pattern including
1989
- // py file even if corresponding pyi exists.
1990
- for (const currentFileInfo of this._sourceFileList) {
1991
- // Make sure we only touch user code to prevent us
1992
- // from accidentally changing third party library or type stub.
1993
- if (!(0, sourceFileInfoUtils_1.isUserCode)(currentFileInfo)) {
1994
- continue;
1995
- }
1996
- // If module name isn't mentioned in the current file, skip the file
1997
- // except the file that got actually renamed/moved.
1998
- // The file that got moved might have relative import paths we need to update.
1999
- const filePath = currentFileInfo.sourceFile.getFilePath();
2000
- const content = (_a = currentFileInfo.sourceFile.getFileContent()) !== null && _a !== void 0 ? _a : '';
2001
- if (filePath !== currentFilePath && content.indexOf(filteringText) < 0) {
2002
- continue;
2003
- }
2004
- this._bindFile(currentFileInfo, content);
2005
- const parseResult = currentFileInfo.sourceFile.getParseResults();
2006
- if (!parseResult) {
2007
- continue;
2008
- }
2009
- renameModuleProvider.renameReferences(parseResult);
2010
- // This operation can consume significant memory, so check
2011
- // for situations where we need to discard the type cache.
2012
- this._handleMemoryHighUsage();
1413
+ // A "shadowed" file is a python source file that has been added to the program because
1414
+ // it "shadows" a type stub file for purposes of finding doc strings and definitions.
1415
+ // We need to track the relationship so if the original type stub is removed from the
1416
+ // program, we can remove the corresponding shadowed file and any files it imports.
1417
+ _addShadowedFile(stubFile, shadowImplPath) {
1418
+ let shadowFileInfo = this.getSourceFileInfo(shadowImplPath);
1419
+ if (!shadowFileInfo) {
1420
+ shadowFileInfo = this.addInterimFile(shadowImplPath);
2013
1421
  }
2014
- }
2015
- _handleMemoryHighUsage() {
2016
- const cacheUsage = this._cacheManager.getCacheUsage();
2017
- // If the total cache has exceeded 75%, determine whether we should empty
2018
- // the cache.
2019
- if (cacheUsage > 0.75) {
2020
- const usedHeapRatio = this._cacheManager.getUsedHeapRatio(this._configOptions.verboseOutput ? this._console : undefined);
2021
- // The type cache uses a Map, which has an absolute limit of 2^24 entries
2022
- // before it will fail. If we cross the 95% mark, we'll empty the cache.
2023
- const absoluteMaxCacheEntryCount = (1 << 24) * 0.9;
2024
- const typeCacheEntryCount = this._evaluator.getTypeCacheEntryCount();
2025
- // If we use more than 90% of the heap size limit, avoid a crash
2026
- // by emptying the type cache.
2027
- if (typeCacheEntryCount > absoluteMaxCacheEntryCount || usedHeapRatio > 0.9) {
2028
- this._cacheManager.emptyCache(this._console);
2029
- }
1422
+ if (!shadowFileInfo.shadows.includes(stubFile)) {
1423
+ shadowFileInfo.shadows.push(stubFile);
2030
1424
  }
1425
+ if (!stubFile.shadowedBy.includes(shadowFileInfo)) {
1426
+ stubFile.shadowedBy.push(shadowFileInfo);
1427
+ }
1428
+ return shadowFileInfo.sourceFile;
2031
1429
  }
2032
- // Discards all cached parse results and file contents to free up memory.
2033
- // It does not discard cached index results or diagnostics for files.
2034
- _discardCachedParseResults() {
2035
- for (const sourceFileInfo of this._sourceFileList) {
2036
- sourceFileInfo.sourceFile.dropParseAndBindInfo();
1430
+ _createInterimFileInfo(filePath) {
1431
+ const importName = this._getImportNameForFile(filePath);
1432
+ const sourceFile = new sourceFile_1.SourceFile(this.fileSystem, filePath, importName,
1433
+ /* isThirdPartyImport */ false,
1434
+ /* isInPyTypedPackage */ false, this._isEditMode, this._console, this._logTracker);
1435
+ const sourceFileInfo = {
1436
+ sourceFile,
1437
+ isTracked: false,
1438
+ isOpenByClient: false,
1439
+ isTypeshedFile: false,
1440
+ isThirdPartyImport: false,
1441
+ isThirdPartyPyTypedPresent: false,
1442
+ diagnosticsVersion: undefined,
1443
+ imports: [],
1444
+ importedBy: [],
1445
+ shadows: [],
1446
+ shadowedBy: [],
1447
+ };
1448
+ return sourceFileInfo;
1449
+ }
1450
+ _createNewEvaluator() {
1451
+ if (this._evaluator) {
1452
+ // We shouldn't need to call this, but there appears to be a bug
1453
+ // in the v8 garbage collector where it's unable to resolve orphaned
1454
+ // objects without us giving it some assistance.
1455
+ this._evaluator.disposeEvaluator();
2037
1456
  }
1457
+ this._evaluator = (0, typeEvaluatorWithTracker_1.createTypeEvaluatorWithTracker)(this._lookUpImport, {
1458
+ printTypeFlags: Program._getPrintTypeFlags(this._configOptions),
1459
+ logCalls: this._configOptions.logTypeEvaluationTime,
1460
+ minimumLoggingThreshold: this._configOptions.typeEvaluationTimeThreshold,
1461
+ evaluateUnknownImportsAsAny: !!this._configOptions.evaluateUnknownImportsAsAny,
1462
+ verifyTypeCacheEvaluatorFlags: !!this._configOptions.internalTestMode,
1463
+ }, this._logTracker, this._configOptions.logTypeEvaluationTime
1464
+ ? (0, tracePrinter_1.createTracePrinter)(this._importResolver.getImportRoots(this._configOptions.findExecEnvironment(this._configOptions.projectRoot)))
1465
+ : undefined);
1466
+ return this._evaluator;
2038
1467
  }
2039
- // Wrapper function that should be used when invoking this._evaluator
2040
- // with a cancellation token. It handles cancellation exceptions and
2041
- // any other unexpected exceptions.
2042
- _runEvaluatorWithCancellationToken(token, callback) {
2043
- try {
2044
- if (token) {
2045
- return this._evaluator.runWithCancellationToken(token, callback);
2046
- }
2047
- else {
2048
- return callback();
2049
- }
1468
+ _parseFile(fileToParse, content, skipFileNeededCheck) {
1469
+ if (!this._isFileNeeded(fileToParse, skipFileNeededCheck) || !fileToParse.sourceFile.isParseRequired()) {
1470
+ return;
2050
1471
  }
2051
- catch (e) {
2052
- // An unexpected exception occurred, potentially leaving the current evaluator
2053
- // in an inconsistent state. Discard it and replace it with a fresh one. It is
2054
- // Cancellation exceptions are known to handle this correctly.
2055
- if (!cancellationUtils_1.OperationCanceledException.is(e)) {
2056
- this._createNewEvaluator();
2057
- }
2058
- throw e;
1472
+ // SourceFile.parse should only be called here in the program, as calling it
1473
+ // elsewhere could break the entire dependency graph maintained by the program.
1474
+ // Other parts of the program should use _parseFile to create ParseResults from
1475
+ // the sourceFile. For standalone parseResults, use parseFile or the Parser directly.
1476
+ if (fileToParse.sourceFile.parse(this._configOptions, this._importResolver, content)) {
1477
+ this._parsedFileCount++;
1478
+ this._updateSourceFileImports(fileToParse, this._configOptions);
2059
1479
  }
2060
- }
2061
- // Returns a list of empty file diagnostic entries for the files
2062
- // that have been removed. This is needed to clear out the
2063
- // errors for files that have been deleted or closed.
2064
- _removeUnneededFiles() {
2065
- const fileDiagnostics = [];
2066
- // If a file is no longer tracked, opened or shadowed, it can
2067
- // be removed from the program.
2068
- for (let i = 0; i < this._sourceFileList.length;) {
2069
- const fileInfo = this._sourceFileList[i];
2070
- if (!this._isFileNeeded(fileInfo)) {
2071
- fileDiagnostics.push({
2072
- filePath: fileInfo.sourceFile.getFilePath(),
2073
- version: fileInfo.sourceFile.getClientVersion(),
2074
- diagnostics: [],
2075
- });
2076
- fileInfo.sourceFile.prepareForClose();
2077
- this._removeSourceFileFromListAndMap(fileInfo.sourceFile.getFilePath(), i);
2078
- // Unlink any imports and remove them from the list if
2079
- // they are no longer referenced.
2080
- fileInfo.imports.forEach((importedFile) => {
2081
- const indexToRemove = importedFile.importedBy.findIndex((fi) => fi === fileInfo);
2082
- if (indexToRemove < 0) {
2083
- return;
2084
- }
2085
- importedFile.importedBy.splice(indexToRemove, 1);
2086
- // See if we need to remove the imported file because it
2087
- // is no longer needed. If its index is >= i, it will be
2088
- // removed when we get to it.
2089
- if (!this._isFileNeeded(importedFile)) {
2090
- const indexToRemove = this._sourceFileList.findIndex((fi) => fi === importedFile);
2091
- if (indexToRemove >= 0 && indexToRemove < i) {
2092
- fileDiagnostics.push({
2093
- filePath: importedFile.sourceFile.getFilePath(),
2094
- version: importedFile.sourceFile.getClientVersion(),
2095
- diagnostics: [],
2096
- });
2097
- importedFile.sourceFile.prepareForClose();
2098
- this._removeSourceFileFromListAndMap(importedFile.sourceFile.getFilePath(), indexToRemove);
2099
- i--;
2100
- }
2101
- }
2102
- });
2103
- // Remove any shadowed files corresponding to this file.
2104
- fileInfo.shadowedBy.forEach((shadowedFile) => {
2105
- shadowedFile.shadows = shadowedFile.shadows.filter((f) => f !== fileInfo);
2106
- });
2107
- fileInfo.shadowedBy = [];
2108
- }
2109
- else {
2110
- // If we're showing the user errors only for open files, clear
2111
- // out the errors for the now-closed file.
2112
- if (!this._shouldCheckFile(fileInfo) && fileInfo.diagnosticsVersion !== undefined) {
2113
- fileDiagnostics.push({
2114
- filePath: fileInfo.sourceFile.getFilePath(),
2115
- version: fileInfo.sourceFile.getClientVersion(),
2116
- diagnostics: [],
2117
- });
2118
- fileInfo.diagnosticsVersion = undefined;
2119
- }
2120
- i++;
2121
- }
1480
+ if (fileToParse.sourceFile.isFileDeleted()) {
1481
+ fileToParse.isTracked = false;
1482
+ // Mark any files that depend on this file as dirty
1483
+ // also. This will retrigger analysis of these other files.
1484
+ const markDirtySet = new Set();
1485
+ this._markFileDirtyRecursive(fileToParse, markDirtySet);
1486
+ // Invalidate the import resolver's cache as well.
1487
+ this._importResolver.invalidateCache();
2122
1488
  }
2123
- return fileDiagnostics;
2124
1489
  }
2125
- _isFileNeeded(fileInfo) {
2126
- if (fileInfo.sourceFile.isFileDeleted()) {
2127
- return false;
1490
+ _getImplicitImports(file) {
1491
+ var _a, _b;
1492
+ // If file is not parsed, then chainedSourceFile, ipythonDisplayImport,
1493
+ // builtinsImport might not exist or incorrect.
1494
+ // They will be added when _parseFile is called and _updateSourceFileImports ran.
1495
+ if (file.builtinsImport === file) {
1496
+ return undefined;
2128
1497
  }
2129
- if (fileInfo.isTracked || fileInfo.isOpenByClient) {
2130
- return true;
1498
+ const tryReturn = (input) => {
1499
+ if (!input || input.sourceFile.isFileDeleted()) {
1500
+ return undefined;
1501
+ }
1502
+ return input;
1503
+ };
1504
+ return (_b = (_a = tryReturn(file.chainedSourceFile)) !== null && _a !== void 0 ? _a : tryReturn(file.ipythonDisplayImport)) !== null && _b !== void 0 ? _b : file.builtinsImport;
1505
+ }
1506
+ _bindImplicitImports(fileToAnalyze, skipFileNeededCheck) {
1507
+ // Get all of the potential imports for this file.
1508
+ const implicitImports = [];
1509
+ const implicitSet = new Set();
1510
+ let nextImplicitImport = this._getImplicitImports(fileToAnalyze);
1511
+ while (nextImplicitImport) {
1512
+ const implicitPath = nextImplicitImport.sourceFile.getFilePath();
1513
+ if (implicitSet.has(implicitPath)) {
1514
+ // We've found a cycle. Break out of the loop.
1515
+ debug.fail(`Found a cycle in implicit imports files for ${implicitPath}`);
1516
+ }
1517
+ implicitSet.add(implicitPath);
1518
+ implicitImports.push(nextImplicitImport);
1519
+ this._parseFile(nextImplicitImport, /* content */ undefined, skipFileNeededCheck);
1520
+ nextImplicitImport = this._getImplicitImports(nextImplicitImport);
2131
1521
  }
2132
- if (fileInfo.shadows.length > 0) {
2133
- return true;
1522
+ if (implicitImports.length === 0) {
1523
+ return;
2134
1524
  }
2135
- if (fileInfo.importedBy.length === 0) {
2136
- return false;
1525
+ // Go in reverse order (so top of chain is first).
1526
+ let implicitImport = implicitImports.pop();
1527
+ while (implicitImport) {
1528
+ // Bind this file, but don't recurse into its imports.
1529
+ this._bindFile(implicitImport, undefined, skipFileNeededCheck, /* isImplicitImport */ true);
1530
+ implicitImport = implicitImports.pop();
2137
1531
  }
2138
- // It's possible for a cycle of files to be imported
2139
- // by a tracked file but then abandoned. The import cycle
2140
- // will keep the entire group "alive" if we don't detect
2141
- // the condition and garbage collect them.
2142
- return this._isImportNeededRecursive(fileInfo, new Set());
2143
1532
  }
2144
- _isImportNeededRecursive(fileInfo, recursionSet) {
2145
- if (fileInfo.isTracked || fileInfo.isOpenByClient || fileInfo.shadows.length > 0) {
2146
- return true;
2147
- }
2148
- const filePath = (0, pathUtils_1.normalizePathCase)(this._fs, fileInfo.sourceFile.getFilePath());
2149
- // Avoid infinite recursion.
2150
- if (recursionSet.has(filePath)) {
2151
- return false;
1533
+ // Binds the specified file and all of its dependencies, recursively. If
1534
+ // it runs out of time, it returns true. If it completes, it returns false.
1535
+ _bindFile(fileToAnalyze, content, skipFileNeededCheck, isImplicitImport) {
1536
+ var _a, _b;
1537
+ if (!this._isFileNeeded(fileToAnalyze, skipFileNeededCheck) || !fileToAnalyze.sourceFile.isBindingRequired()) {
1538
+ return;
2152
1539
  }
2153
- recursionSet.add(filePath);
2154
- for (const importerInfo of fileInfo.importedBy) {
2155
- if (this._isImportNeededRecursive(importerInfo, recursionSet)) {
2156
- return true;
1540
+ this._parseFile(fileToAnalyze, content, skipFileNeededCheck);
1541
+ // Create a function to get the scope info.
1542
+ const getScopeIfAvailable = (fileInfo) => {
1543
+ if (!fileInfo || fileInfo === fileToAnalyze) {
1544
+ return undefined;
1545
+ }
1546
+ // If the file was deleted, there's no scope to return.
1547
+ if (fileInfo.sourceFile.isFileDeleted()) {
1548
+ return undefined;
1549
+ }
1550
+ const parseResults = fileInfo.sourceFile.getParseResults();
1551
+ if (!parseResults) {
1552
+ return undefined;
1553
+ }
1554
+ // File should already be bound because of the chained file binding above.
1555
+ const scope = AnalyzerNodeInfo.getScope(parseResults.parseTree);
1556
+ return scope;
1557
+ };
1558
+ let builtinsScope;
1559
+ if (fileToAnalyze.builtinsImport && fileToAnalyze.builtinsImport !== fileToAnalyze) {
1560
+ // Bind all of the implicit imports first. So we don't recurse into them.
1561
+ if (!isImplicitImport) {
1562
+ this._bindImplicitImports(fileToAnalyze);
2157
1563
  }
1564
+ // If it is not builtin module itself, we need to parse and bind
1565
+ // the ipython display import if required. Otherwise, get builtin module.
1566
+ builtinsScope =
1567
+ (_b = (_a = getScopeIfAvailable(fileToAnalyze.chainedSourceFile)) !== null && _a !== void 0 ? _a : getScopeIfAvailable(fileToAnalyze.ipythonDisplayImport)) !== null && _b !== void 0 ? _b : getScopeIfAvailable(fileToAnalyze.builtinsImport);
2158
1568
  }
2159
- return false;
1569
+ let futureImports = fileToAnalyze.sourceFile.getParseResults().futureImports;
1570
+ if (fileToAnalyze.chainedSourceFile) {
1571
+ futureImports = this._getEffectiveFutureImports(futureImports, fileToAnalyze.chainedSourceFile);
1572
+ }
1573
+ fileToAnalyze.effectiveFutureImports = futureImports.size > 0 ? futureImports : undefined;
1574
+ fileToAnalyze.sourceFile.bind(this._configOptions, this._lookUpImport, builtinsScope, futureImports);
2160
1575
  }
2161
- _createSourceMapper(execEnv, token, from, mapCompiled, preferStubs) {
2162
- const sourceMapper = new sourceMapper_1.SourceMapper(this._importResolver, execEnv, this._evaluator, (stubFilePath, implFilePath) => {
2163
- let stubFileInfo = this.getSourceFileInfo(stubFilePath);
2164
- if (!stubFileInfo) {
2165
- // Special case for import statement like "import X.Y". The SourceFile
2166
- // for X might not be in memory since import `X.Y` only brings in Y.
2167
- stubFileInfo = this.addInterimFile(stubFilePath);
1576
+ _getEffectiveFutureImports(futureImports, chainedSourceFile) {
1577
+ var _a;
1578
+ const effectiveFutureImports = new Set(futureImports);
1579
+ (_a = chainedSourceFile.effectiveFutureImports) === null || _a === void 0 ? void 0 : _a.forEach((value) => {
1580
+ effectiveFutureImports.add(value);
1581
+ });
1582
+ return effectiveFutureImports;
1583
+ }
1584
+ // Build a map of all modules within this program and the module-
1585
+ // level scope that contains the symbol table for the module.
1586
+ _buildModuleSymbolsMap(sourceFileToExclude, libraryMap, includeSymbolsFromIndices, token) {
1587
+ // If we have library map, always use the map for library symbols.
1588
+ const predicate = (s) => {
1589
+ if (!libraryMap) {
1590
+ // We don't have any prebuilt indices, so we need to include
1591
+ // all files.
1592
+ return true;
2168
1593
  }
2169
- this._addShadowedFile(stubFileInfo, implFilePath);
2170
- return this.getBoundSourceFile(implFilePath);
2171
- }, (f) => {
2172
- let fileInfo = this.getBoundSourceFileInfo(f);
2173
- if (!fileInfo) {
2174
- // Special case for import statement like "import X.Y". The SourceFile
2175
- // for X might not be in memory since import `X.Y` only brings in Y.
2176
- fileInfo = this.addInterimFile(f);
2177
- // Even though this file is not referenced by anything, make sure
2178
- // we have a parse tree for the doc string.
2179
- this._parseFile(fileInfo, /* content */ undefined, /* force */ true);
1594
+ if (!this._configOptions.indexing) {
1595
+ // We have some prebuilt indices such as stdlib, but indexing is disabled.
1596
+ // Include files we don't have prebuilt indices.
1597
+ return libraryMap.get(s.sourceFile.getFilePath()) === undefined;
2180
1598
  }
2181
- return fileInfo;
2182
- }, mapCompiled !== null && mapCompiled !== void 0 ? mapCompiled : false, preferStubs !== null && preferStubs !== void 0 ? preferStubs : false, from, token);
2183
- return sourceMapper;
1599
+ // We have prebuilt indices for third party libraries. Include only
1600
+ // user files.
1601
+ return (0, sourceFileInfoUtils_1.isUserCode)(s);
1602
+ };
1603
+ // Only include import alias from user files if indexing is off for now.
1604
+ // Currently, when indexing is off, we don't do import alias deduplication, so
1605
+ // adding import alias is cheap. But when indexing is on, we do deduplication, which
1606
+ // require resolveAliasDeclaration that can cause more files to be parsed and bound.
1607
+ return (0, autoImporter_1.buildModuleSymbolsMap)(this._sourceFileList.filter((s) => s !== sourceFileToExclude && predicate(s)), includeSymbolsFromIndices, token);
2184
1608
  }
2185
- _isImportAllowed(importer, importResult, isImportStubFile) {
2186
- // Don't import native libs. We don't want to track these files,
2187
- // and we definitely don't want to attempt to parse them.
2188
- if (importResult.isNativeLib) {
2189
- return false;
1609
+ _shouldCheckFile(fileInfo) {
1610
+ // Always do a full checking for a file that's open in the editor.
1611
+ if (fileInfo.isOpenByClient) {
1612
+ return true;
2190
1613
  }
2191
- let thirdPartyImportAllowed = this._configOptions.useLibraryCodeForTypes ||
2192
- (importResult.importType === 1 /* ThirdParty */ && !!importResult.pyTypedInfo) ||
2193
- (importResult.importType === 2 /* Local */ && importer.isThirdPartyPyTypedPresent);
2194
- if (importResult.importType === 1 /* ThirdParty */ ||
2195
- (importer.isThirdPartyImport && importResult.importType === 2 /* Local */)) {
2196
- if (this._allowedThirdPartyImports) {
2197
- if (importResult.isRelative) {
2198
- // If it's a relative import, we'll allow it because the
2199
- // importer was already deemed to be allowed.
2200
- thirdPartyImportAllowed = true;
2201
- }
2202
- else if (this._allowedThirdPartyImports.some((importName) => {
2203
- // If this import name is the one that was explicitly
2204
- // allowed or is a child of that import name,
2205
- // it's considered allowed.
2206
- if (importResult.importName === importName) {
2207
- return true;
2208
- }
2209
- if (importResult.importName.startsWith(importName + '.')) {
2210
- return true;
2211
- }
2212
- return false;
2213
- })) {
2214
- thirdPartyImportAllowed = true;
2215
- }
2216
- }
2217
- else if (importer.isThirdPartyImport && this._configOptions.useLibraryCodeForTypes) {
2218
- // If the importing file is a third-party import, allow importing of
2219
- // additional third-party imports. This supports the case where the importer
2220
- // is in a py.typed library but is importing from another non-py.typed
2221
- // library. It also supports the case where someone explicitly opens a
2222
- // library source file in their editor.
2223
- thirdPartyImportAllowed = true;
2224
- }
2225
- else if (importResult.isNamespacePackage &&
2226
- importResult.filteredImplicitImports.some((implicitImport) => !!implicitImport.pyTypedInfo)) {
2227
- // Handle the case where the import targets a namespace package, and a
2228
- // submodule contained within it has a py.typed marker.
2229
- thirdPartyImportAllowed = true;
2230
- }
2231
- // Some libraries ship with stub files that import from non-stubs. Don't
2232
- // explore those.
2233
- // Don't explore any third-party files unless they're type stub files
2234
- // or we've been told explicitly that third-party imports are OK.
2235
- if (!isImportStubFile) {
2236
- return thirdPartyImportAllowed;
2237
- }
1614
+ // If the file isn't currently open, only perform full checking for
1615
+ // files that are tracked, and only if the checkOnlyOpenFiles is disabled.
1616
+ if (!this._configOptions.checkOnlyOpenFiles && fileInfo.isTracked) {
1617
+ return true;
2238
1618
  }
2239
- return true;
1619
+ return false;
2240
1620
  }
2241
- _updateSourceFileImports(sourceFileInfo, options) {
2242
- const filesAdded = [];
2243
- // Get the new list of imports and see if it changed from the last
2244
- // list of imports for this file.
2245
- const imports = sourceFileInfo.sourceFile.getImports();
2246
- // Create a local function that determines whether the import should
2247
- // be considered a "third-party import" and whether it is coming from
2248
- // a third-party package that claims to be typed. An import is
2249
- // considered third-party if it is external to the importer
2250
- // or is internal but the importer is itself a third-party package.
2251
- const getThirdPartyImportInfo = (importResult) => {
2252
- let isThirdPartyImport = false;
2253
- let isPyTypedPresent = false;
2254
- if (importResult.importType === 1 /* ThirdParty */) {
2255
- isThirdPartyImport = true;
2256
- if (importResult.pyTypedInfo) {
2257
- isPyTypedPresent = true;
2258
- }
1621
+ _checkTypes(fileToCheck, token) {
1622
+ return this._logTracker.log(`analyzing: ${fileToCheck.sourceFile.getFilePath()}`, (logState) => {
1623
+ // If the file isn't needed because it was eliminated from the
1624
+ // transitive closure or deleted, skip the file rather than wasting
1625
+ // time on it.
1626
+ if (!this._isFileNeeded(fileToCheck)) {
1627
+ logState.suppress();
1628
+ return false;
2259
1629
  }
2260
- else if (sourceFileInfo.isThirdPartyImport && importResult.importType === 2 /* Local */) {
2261
- isThirdPartyImport = true;
2262
- if (sourceFileInfo.isThirdPartyPyTypedPresent) {
2263
- isPyTypedPresent = true;
2264
- }
1630
+ if (!fileToCheck.sourceFile.isCheckingRequired()) {
1631
+ logState.suppress();
1632
+ return false;
2265
1633
  }
2266
- return {
2267
- isThirdPartyImport,
2268
- isPyTypedPresent,
2269
- };
2270
- };
2271
- // Create a map of unique imports, since imports can appear more than once.
2272
- const newImportPathMap = new Map();
2273
- // Add chained source file as import if it exists.
2274
- if (sourceFileInfo.chainedSourceFile) {
2275
- if (sourceFileInfo.chainedSourceFile.sourceFile.isFileDeleted()) {
2276
- sourceFileInfo.chainedSourceFile = undefined;
1634
+ if (!this._shouldCheckFile(fileToCheck)) {
1635
+ logState.suppress();
1636
+ return false;
2277
1637
  }
2278
- else {
2279
- const filePath = sourceFileInfo.chainedSourceFile.sourceFile.getFilePath();
2280
- newImportPathMap.set((0, pathUtils_1.normalizePathCase)(this._fs, filePath), {
2281
- path: filePath,
2282
- isTypeshedFile: false,
2283
- isThirdPartyImport: false,
2284
- isPyTypedPresent: false,
2285
- });
1638
+ this._bindFile(fileToCheck);
1639
+ if (this._preCheckCallback) {
1640
+ const parseResults = fileToCheck.sourceFile.getParseResults();
1641
+ if (parseResults) {
1642
+ this._preCheckCallback(parseResults, this._evaluator);
1643
+ }
2286
1644
  }
2287
- }
2288
- imports.forEach((importResult) => {
2289
- if (importResult.isImportFound) {
2290
- if (this._isImportAllowed(sourceFileInfo, importResult, importResult.isStubFile)) {
2291
- if (importResult.resolvedPaths.length > 0) {
2292
- const filePath = importResult.resolvedPaths[importResult.resolvedPaths.length - 1];
2293
- if (filePath) {
2294
- const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
2295
- newImportPathMap.set((0, pathUtils_1.normalizePathCase)(this._fs, filePath), {
2296
- path: filePath,
2297
- isTypeshedFile: !!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
2298
- isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
2299
- isPyTypedPresent: thirdPartyTypeInfo.isPyTypedPresent,
2300
- });
1645
+ if (!this._disableChecker) {
1646
+ // For ipython, make sure we check all its dependent files first since
1647
+ // their results can affect this file's result.
1648
+ let dependentFiles = undefined;
1649
+ if (fileToCheck.sourceFile.getIPythonMode() === sourceFile_1.IPythonMode.CellDocs) {
1650
+ dependentFiles = [];
1651
+ const importedByFiles = (0, sourceFileInfoUtils_1.collectImportedByFiles)(fileToCheck);
1652
+ for (const file of importedByFiles) {
1653
+ if (!(0, sourceFileInfoUtils_1.isUserCode)(file)) {
1654
+ continue;
2301
1655
  }
2302
- }
2303
- }
2304
- importResult.filteredImplicitImports.forEach((implicitImport) => {
2305
- if (this._isImportAllowed(sourceFileInfo, importResult, implicitImport.isStubFile)) {
2306
- if (!implicitImport.isNativeLib) {
2307
- const thirdPartyTypeInfo = getThirdPartyImportInfo(importResult);
2308
- newImportPathMap.set((0, pathUtils_1.normalizePathCase)(this._fs, implicitImport.path), {
2309
- path: implicitImport.path,
2310
- isTypeshedFile: !!importResult.isStdlibTypeshedFile || !!importResult.isThirdPartyTypeshedFile,
2311
- isThirdPartyImport: thirdPartyTypeInfo.isThirdPartyImport,
2312
- isPyTypedPresent: thirdPartyTypeInfo.isPyTypedPresent,
2313
- });
1656
+ // If the file is already analyzed, it will be no op.
1657
+ // And make sure we don't dump parse tree and etc while
1658
+ // recursively calling checker. Otherwise, inner check
1659
+ // can dump parse tree required by outer check.
1660
+ const handle = this._cacheManager.pauseTracking();
1661
+ try {
1662
+ this._checkTypes(file, token);
2314
1663
  }
2315
- }
2316
- });
2317
- // If the stub was found but the non-stub (source) file was not, dump
2318
- // the failure to the log for diagnostic purposes.
2319
- if (importResult.nonStubImportResult && !importResult.nonStubImportResult.isImportFound) {
2320
- // We'll skip this for imports from within stub files and imports that target
2321
- // stdlib typeshed stubs because many of these are known to not have
2322
- // associated source files, and we don't want to fill the logs with noise.
2323
- if (!sourceFileInfo.sourceFile.isStubFile() && !importResult.isStdlibTypeshedFile) {
2324
- if (options.verboseOutput) {
2325
- this._console.info(`Could not resolve source for '${importResult.importName}' ` +
2326
- `in file '${sourceFileInfo.sourceFile.getFilePath()}'`);
2327
- if (importResult.nonStubImportResult.importFailureInfo) {
2328
- importResult.nonStubImportResult.importFailureInfo.forEach((diag) => {
2329
- this._console.info(` ${diag}`);
2330
- });
2331
- }
1664
+ finally {
1665
+ handle.dispose();
1666
+ }
1667
+ const parseResults = file.sourceFile.getParseResults();
1668
+ if (parseResults) {
1669
+ dependentFiles.push(parseResults);
2332
1670
  }
2333
1671
  }
2334
1672
  }
1673
+ const execEnv = this._configOptions.findExecEnvironment(fileToCheck.sourceFile.getFilePath());
1674
+ fileToCheck.sourceFile.check(this._importResolver, this._evaluator, this._createSourceMapper(execEnv, token, fileToCheck), dependentFiles);
2335
1675
  }
2336
- else if (options.verboseOutput) {
2337
- this._console.info(`Could not import '${importResult.importName}' ` +
2338
- `in file '${sourceFileInfo.sourceFile.getFilePath()}'`);
2339
- if (importResult.importFailureInfo) {
2340
- importResult.importFailureInfo.forEach((diag) => {
2341
- this._console.info(` ${diag}`);
1676
+ // For very large programs, we may need to discard the evaluator and
1677
+ // its cached types to avoid running out of heap space.
1678
+ this._handleMemoryHighUsage();
1679
+ // Detect import cycles that involve the file.
1680
+ if (this._configOptions.diagnosticRuleSet.reportImportCycles !== 'none') {
1681
+ // Don't detect import cycles when doing type stub generation. Some
1682
+ // third-party modules are pretty convoluted.
1683
+ if (!this._allowedThirdPartyImports) {
1684
+ // We need to force all of the files to be parsed and build
1685
+ // a closure map for the files.
1686
+ const closureMap = new Map();
1687
+ this._getImportsRecursive(fileToCheck, closureMap, 0);
1688
+ closureMap.forEach((file) => {
1689
+ timing_1.timingStats.cycleDetectionTime.timeOperation(() => {
1690
+ const filesVisitedMap = new Map();
1691
+ if (!this._detectAndReportImportCycles(file, filesVisitedMap)) {
1692
+ // If no cycles were found in any of the files we visited,
1693
+ // set a flag to indicates that we don't need to visit them again
1694
+ // on subsequent cycle checks.
1695
+ filesVisitedMap.forEach((sourceFileInfo) => {
1696
+ sourceFileInfo.sourceFile.setNoCircularDependencyConfirmed();
1697
+ });
1698
+ }
1699
+ });
2342
1700
  });
2343
1701
  }
2344
1702
  }
1703
+ return true;
2345
1704
  });
2346
- const updatedImportMap = new Map();
2347
- sourceFileInfo.imports.forEach((importInfo) => {
2348
- const oldFilePath = (0, pathUtils_1.normalizePathCase)(this._fs, importInfo.sourceFile.getFilePath());
2349
- // A previous import was removed.
2350
- if (!newImportPathMap.has(oldFilePath)) {
2351
- importInfo.importedBy = importInfo.importedBy.filter((fi) => (0, pathUtils_1.normalizePathCase)(this._fs, fi.sourceFile.getFilePath()) !==
2352
- (0, pathUtils_1.normalizePathCase)(this._fs, sourceFileInfo.sourceFile.getFilePath()));
1705
+ }
1706
+ // Builds a map of files that includes the specified file and all of the files
1707
+ // it imports (recursively) and ensures that all such files. If any of these files
1708
+ // have already been checked (they and their recursive imports have completed the
1709
+ // check phase), they are not included in the results.
1710
+ _getImportsRecursive(file, closureMap, recursionCount) {
1711
+ // If the file is already in the closure map, we found a cyclical
1712
+ // dependency. Don't recur further.
1713
+ const filePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, file.sourceFile.getFilePath());
1714
+ if (closureMap.has(filePath)) {
1715
+ return;
1716
+ }
1717
+ // If the import chain is too long, emit an error. Otherwise we
1718
+ // risk blowing the stack.
1719
+ if (recursionCount > _maxImportDepth) {
1720
+ file.sourceFile.setHitMaxImportDepth(_maxImportDepth);
1721
+ return;
1722
+ }
1723
+ // Add the file to the closure map.
1724
+ closureMap.set(filePath, file);
1725
+ // If this file hasn't already been parsed, parse it now. This will
1726
+ // discover any files it imports. Skip this if the file is part
1727
+ // of a library. We'll assume that no cycles will be generated from
1728
+ // library code or typeshed stubs.
1729
+ if ((0, sourceFileInfoUtils_1.isUserCode)(file)) {
1730
+ this._parseFile(file);
1731
+ }
1732
+ // Recursively add the file's imports.
1733
+ for (const importedFileInfo of file.imports) {
1734
+ this._getImportsRecursive(importedFileInfo, closureMap, recursionCount + 1);
1735
+ }
1736
+ }
1737
+ _detectAndReportImportCycles(sourceFileInfo, filesVisited, dependencyChain = [], dependencyMap = new Map()) {
1738
+ // Don't bother checking for typestub files or third-party files.
1739
+ if (sourceFileInfo.sourceFile.isStubFile() || sourceFileInfo.isThirdPartyImport) {
1740
+ return false;
1741
+ }
1742
+ // If we've already confirmed that this source file isn't part of a
1743
+ // cycle, we can skip it entirely.
1744
+ if (sourceFileInfo.sourceFile.isNoCircularDependencyConfirmed()) {
1745
+ return false;
1746
+ }
1747
+ const filePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, sourceFileInfo.sourceFile.getFilePath());
1748
+ filesVisited.set(filePath, sourceFileInfo);
1749
+ let detectedCycle = false;
1750
+ if (dependencyMap.has(filePath)) {
1751
+ // We detect a cycle (partial or full). A full cycle is one that is
1752
+ // rooted in the file at the start of our dependency chain. A partial
1753
+ // cycle loops back on some other file in the dependency chain. We
1754
+ // will report only full cycles here and leave the reporting of
1755
+ // partial cycles to other passes.
1756
+ detectedCycle = true;
1757
+ // Look for chains at least two in length. A file that contains
1758
+ // an "import . from X" will technically create a cycle with
1759
+ // itself, but those are not interesting to report.
1760
+ if (dependencyChain.length > 1 && sourceFileInfo === dependencyChain[0]) {
1761
+ this._logImportCycle(dependencyChain);
2353
1762
  }
2354
- else {
2355
- updatedImportMap.set(oldFilePath, importInfo);
1763
+ }
1764
+ else {
1765
+ // If we've already checked this dependency along
1766
+ // some other path, we can skip it.
1767
+ if (dependencyMap.has(filePath)) {
1768
+ return false;
2356
1769
  }
2357
- });
2358
- // See if there are any new imports to be added.
2359
- newImportPathMap.forEach((importInfo, normalizedImportPath) => {
2360
- if (!updatedImportMap.has(normalizedImportPath)) {
2361
- // We found a new import to add. See if it's already part
2362
- // of the program.
2363
- let importedFileInfo = this.getSourceFileInfo(importInfo.path);
2364
- if (!importedFileInfo) {
2365
- const importName = this._getImportNameForFile(importInfo.path);
2366
- const sourceFile = new sourceFile_1.SourceFile(this._fs, importInfo.path, importName, importInfo.isThirdPartyImport, importInfo.isPyTypedPresent, this._console, this._logTracker);
2367
- importedFileInfo = {
2368
- sourceFile,
2369
- isTracked: false,
2370
- isOpenByClient: false,
2371
- isTypeshedFile: importInfo.isTypeshedFile,
2372
- isThirdPartyImport: importInfo.isThirdPartyImport,
2373
- isThirdPartyPyTypedPresent: importInfo.isPyTypedPresent,
2374
- diagnosticsVersion: undefined,
2375
- imports: [],
2376
- importedBy: [],
2377
- shadows: [],
2378
- shadowedBy: [],
2379
- };
2380
- this._addToSourceFileListAndMap(importedFileInfo);
2381
- filesAdded.push(importedFileInfo);
1770
+ // We use both a map (for fast lookups) and a list
1771
+ // (for ordering information). Set the dependency map
1772
+ // entry to true to indicate that we're actively exploring
1773
+ // that dependency.
1774
+ dependencyMap.set(filePath, true);
1775
+ dependencyChain.push(sourceFileInfo);
1776
+ for (const imp of sourceFileInfo.imports) {
1777
+ if (this._detectAndReportImportCycles(imp, filesVisited, dependencyChain, dependencyMap)) {
1778
+ detectedCycle = true;
2382
1779
  }
2383
- importedFileInfo.importedBy.push(sourceFileInfo);
2384
- updatedImportMap.set(normalizedImportPath, importedFileInfo);
2385
- }
2386
- });
2387
- // Update the imports list. It should now map the set of imports
2388
- // specified by the source file.
2389
- sourceFileInfo.imports = [];
2390
- newImportPathMap.forEach((_, path) => {
2391
- if (this.getSourceFileInfo(path)) {
2392
- sourceFileInfo.imports.push(this.getSourceFileInfo(path));
2393
1780
  }
2394
- });
2395
- // Resolve the builtins import for the file. This needs to be
2396
- // analyzed before the file can be analyzed.
2397
- sourceFileInfo.builtinsImport = undefined;
2398
- const builtinsImport = sourceFileInfo.sourceFile.getBuiltinsImport();
2399
- if (builtinsImport && builtinsImport.isImportFound) {
2400
- const resolvedBuiltinsPath = builtinsImport.resolvedPaths[builtinsImport.resolvedPaths.length - 1];
2401
- sourceFileInfo.builtinsImport = this.getSourceFileInfo(resolvedBuiltinsPath);
2402
- }
2403
- // Resolve the ipython display import for the file. This needs to be
2404
- // analyzed before the file can be analyzed.
2405
- sourceFileInfo.ipythonDisplayImport = undefined;
2406
- const ipythonDisplayImport = sourceFileInfo.sourceFile.getIPythonDisplayImport();
2407
- if (ipythonDisplayImport && ipythonDisplayImport.isImportFound) {
2408
- const resolvedIPythonDisplayPath = ipythonDisplayImport.resolvedPaths[ipythonDisplayImport.resolvedPaths.length - 1];
2409
- sourceFileInfo.ipythonDisplayImport = this.getSourceFileInfo(resolvedIPythonDisplayPath);
1781
+ // Set the dependencyMap entry to false to indicate that we have
1782
+ // already explored this file and don't need to explore it again.
1783
+ dependencyMap.set(filePath, false);
1784
+ dependencyChain.pop();
2410
1785
  }
2411
- return filesAdded;
1786
+ return detectedCycle;
2412
1787
  }
2413
- _removeSourceFileFromListAndMap(filePath, indexToRemove) {
2414
- this._sourceFileMap.delete((0, pathUtils_1.normalizePathCase)(this._fs, filePath));
2415
- this._sourceFileList.splice(indexToRemove, 1);
1788
+ _logImportCycle(dependencyChain) {
1789
+ const circDep = new circularDependency_1.CircularDependency();
1790
+ dependencyChain.forEach((sourceFileInfo) => {
1791
+ circDep.appendPath(sourceFileInfo.sourceFile.getFilePath());
1792
+ });
1793
+ circDep.normalizeOrder();
1794
+ const firstFilePath = circDep.getPaths()[0];
1795
+ const firstSourceFile = this.getSourceFileInfo(firstFilePath);
1796
+ (0, debug_1.assert)(firstSourceFile !== undefined);
1797
+ firstSourceFile.sourceFile.addCircularDependency(circDep);
2416
1798
  }
2417
- _addToSourceFileListAndMap(fileInfo) {
2418
- const filePath = (0, pathUtils_1.normalizePathCase)(this._fs, fileInfo.sourceFile.getFilePath());
2419
- // We should never add a file with the same path twice.
2420
- (0, debug_1.assert)(!this._sourceFileMap.has(filePath));
2421
- this._sourceFileList.push(fileInfo);
2422
- this._sourceFileMap.set(filePath, fileInfo);
1799
+ _markFileDirtyRecursive(sourceFileInfo, markSet, forceRebinding = false) {
1800
+ const filePath = (0, pathUtils_1.normalizePathCase)(this.fileSystem, sourceFileInfo.sourceFile.getFilePath());
1801
+ // Don't mark it again if it's already been visited.
1802
+ if (markSet.has(filePath)) {
1803
+ return;
1804
+ }
1805
+ sourceFileInfo.sourceFile.markReanalysisRequired(forceRebinding);
1806
+ markSet.add(filePath);
1807
+ sourceFileInfo.importedBy.forEach((dep) => {
1808
+ // Changes on chained source file can change symbols in the symbol table and
1809
+ // dependencies on the dependent file. Force rebinding.
1810
+ const forceRebinding = dep.chainedSourceFile === sourceFileInfo;
1811
+ this._markFileDirtyRecursive(dep, markSet, forceRebinding);
1812
+ });
1813
+ // Change in the current file could impact checker result of chainedSourceFile such as unused symbols.
1814
+ let chainedSourceFile = sourceFileInfo.chainedSourceFile;
1815
+ while (chainedSourceFile) {
1816
+ if (chainedSourceFile.sourceFile.isCheckingRequired()) {
1817
+ // If the file is marked for checking, its chained one should be marked
1818
+ // as well. Stop here.
1819
+ return;
1820
+ }
1821
+ chainedSourceFile.sourceFile.markReanalysisRequired(/* forceRebinding */ false);
1822
+ chainedSourceFile = chainedSourceFile.chainedSourceFile;
1823
+ }
2423
1824
  }
2424
1825
  }
2425
1826
  exports.Program = Program;